Strip trailing directory slash
[kugel-rb.git] / apps / playlist.c
blob651a5afeea74bb05f0a726dd1aeaea40e906d3cd
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 "playlist.h"
72 #include "file.h"
73 #include "dir.h"
74 #include "sprintf.h"
75 #include "debug.h"
76 #include "mpeg.h"
77 #include "lcd.h"
78 #include "kernel.h"
79 #include "settings.h"
80 #include "status.h"
81 #include "applimits.h"
82 #include "screens.h"
83 #include "buffer.h"
84 #include "atoi.h"
85 #include "misc.h"
86 #include "button.h"
87 #include "filetree.h"
88 #ifdef HAVE_LCD_BITMAP
89 #include "icons.h"
90 #include "widgets.h"
91 #endif
93 #include "lang.h"
94 #include "talk.h"
96 #define PLAYLIST_CONTROL_FILE ROCKBOX_DIR "/.playlist_control"
97 #define PLAYLIST_CONTROL_FILE_VERSION 2
100 Each playlist index has a flag associated with it which identifies what
101 type of track it is. These flags are stored in the 3 high order bits of
102 the index.
104 NOTE: This limits the playlist file size to a max of 512M.
106 Bits 31-30:
107 00 = Playlist track
108 01 = Track was prepended into playlist
109 10 = Track was inserted into playlist
110 11 = Track was appended into playlist
111 Bit 29:
112 0 = Added track
113 1 = Queued track
115 #define PLAYLIST_SEEK_MASK 0x1FFFFFFF
116 #define PLAYLIST_INSERT_TYPE_MASK 0xC0000000
117 #define PLAYLIST_QUEUE_MASK 0x20000000
119 #define PLAYLIST_INSERT_TYPE_PREPEND 0x40000000
120 #define PLAYLIST_INSERT_TYPE_INSERT 0x80000000
121 #define PLAYLIST_INSERT_TYPE_APPEND 0xC0000000
123 #define PLAYLIST_QUEUED 0x20000000
125 #define PLAYLIST_DISPLAY_COUNT 10
127 static struct playlist_info current_playlist;
128 static char now_playing[MAX_PATH+1];
130 static void empty_playlist(struct playlist_info* playlist, bool resume);
131 static void new_playlist(struct playlist_info* playlist, const char *dir,
132 const char *file);
133 static void create_control(struct playlist_info* playlist);
134 static int check_control(struct playlist_info* playlist);
135 static void update_playlist_filename(struct playlist_info* playlist,
136 const char *dir, const char *file);
137 static int add_indices_to_playlist(struct playlist_info* playlist,
138 char* buffer, int buflen);
139 static int add_track_to_playlist(struct playlist_info* playlist,
140 const char *filename, int position,
141 bool queue, int seek_pos);
142 static int add_directory_to_playlist(struct playlist_info* playlist,
143 const char *dirname, int *position, bool queue,
144 int *count, bool recurse);
145 static int remove_track_from_playlist(struct playlist_info* playlist,
146 int position, bool write);
147 static int randomise_playlist(struct playlist_info* playlist,
148 unsigned int seed, bool start_current,
149 bool write);
150 static int sort_playlist(struct playlist_info* playlist, bool start_current,
151 bool write);
152 static int get_next_index(const struct playlist_info* playlist, int steps);
153 static void find_and_set_playlist_index(struct playlist_info* playlist,
154 unsigned int seek);
155 static int compare(const void* p1, const void* p2);
156 static int get_filename(struct playlist_info* playlist, int seek,
157 bool control_file, char *buf, int buf_length);
158 static int format_track_path(char *dest, char *src, int buf_length, int max,
159 char *dir);
160 static void display_playlist_count(int count, const char *fmt);
161 static void display_buffer_full(void);
162 static int flush_pending_control(struct playlist_info* playlist);
163 static int rotate_index(const struct playlist_info* playlist, int index);
166 * remove any files and indices associated with the playlist
168 static void empty_playlist(struct playlist_info* playlist, bool resume)
170 playlist->filename[0] = '\0';
172 if(playlist->fd >= 0)
173 /* If there is an already open playlist, close it. */
174 close(playlist->fd);
175 playlist->fd = -1;
177 if(playlist->control_fd >= 0)
178 close(playlist->control_fd);
179 playlist->control_fd = -1;
180 playlist->control_created = false;
182 playlist->in_ram = false;
184 if (playlist->buffer)
185 playlist->buffer[0] = 0;
187 playlist->buffer_end_pos = 0;
189 playlist->index = 0;
190 playlist->first_index = 0;
191 playlist->amount = 0;
192 playlist->last_insert_pos = -1;
193 playlist->seed = 0;
194 playlist->shuffle_modified = false;
195 playlist->deleted = false;
196 playlist->num_inserted_tracks = 0;
197 playlist->shuffle_flush = false;
199 if (!resume && playlist->current)
201 /* start with fresh playlist control file when starting new
202 playlist */
203 create_control(playlist);
205 /* Reset resume settings */
206 global_settings.resume_first_index = 0;
207 global_settings.resume_seed = -1;
212 * Initialize a new playlist for viewing/editing/playing. dir is the
213 * directory where the playlist is located and file is the filename.
215 static void new_playlist(struct playlist_info* playlist, const char *dir,
216 const char *file)
218 empty_playlist(playlist, false);
220 if (!file)
222 file = "";
224 if (dir && playlist->current) /* !current cannot be in_ram */
225 playlist->in_ram = true;
226 else
227 dir = ""; /* empty playlist */
230 update_playlist_filename(playlist, dir, file);
232 if (playlist->control_fd >= 0)
234 if (fprintf(playlist->control_fd, "P:%d:%s:%s\n",
235 PLAYLIST_CONTROL_FILE_VERSION, dir, file) > 0)
236 fsync(playlist->control_fd);
237 else
238 splash(HZ*2, true, str(LANG_PLAYLIST_CONTROL_UPDATE_ERROR));
243 * create control file for playlist
245 static void create_control(struct playlist_info* playlist)
247 playlist->control_fd = creat(playlist->control_filename, 0000200);
248 if (playlist->control_fd < 0)
250 if (check_rockboxdir())
252 splash(HZ*2, true, "%s (%d)", str(LANG_PLAYLIST_CONTROL_ACCESS_ERROR),
253 playlist->control_fd);
255 playlist->control_created = false;
257 else
259 playlist->control_created = true;
264 * validate the control file. This may include creating/initializing it if
265 * necessary;
267 static int check_control(struct playlist_info* playlist)
269 if (!playlist->control_created)
271 create_control(playlist);
273 if (playlist->control_fd >= 0)
275 char* dir = playlist->filename;
276 char* file = playlist->filename+playlist->dirlen;
277 char c = playlist->filename[playlist->dirlen-1];
279 playlist->filename[playlist->dirlen-1] = '\0';
281 if (fprintf(playlist->control_fd, "P:%d:%s:%s\n",
282 PLAYLIST_CONTROL_FILE_VERSION, dir, file) > 0)
283 fsync(playlist->control_fd);
284 else
285 splash(HZ*2, true, str(LANG_PLAYLIST_CONTROL_UPDATE_ERROR));
287 playlist->filename[playlist->dirlen-1] = c;
291 if (playlist->control_fd < 0)
292 return -1;
294 return 0;
298 * store directory and name of playlist file
300 static void update_playlist_filename(struct playlist_info* playlist,
301 const char *dir, const char *file)
303 char *sep="";
304 int dirlen = strlen(dir);
306 /* If the dir does not end in trailing slash, we use a separator.
307 Otherwise we don't. */
308 if('/' != dir[dirlen-1])
310 sep="/";
311 dirlen++;
314 playlist->dirlen = dirlen;
316 snprintf(playlist->filename, sizeof(playlist->filename),
317 "%s%s%s",
318 dir, sep, file);
322 * calculate track offsets within a playlist file
324 static int add_indices_to_playlist(struct playlist_info* playlist,
325 char* buffer, int buflen)
327 unsigned int nread;
328 unsigned int i = 0;
329 unsigned int count = 0;
330 bool store_index;
331 unsigned char *p;
333 if(-1 == playlist->fd)
334 playlist->fd = open(playlist->filename, O_RDONLY);
335 if(playlist->fd < 0)
336 return -1; /* failure */
338 #ifdef HAVE_LCD_BITMAP
339 if(global_settings.statusbar)
340 lcd_setmargins(0, STATUSBAR_HEIGHT);
341 else
342 lcd_setmargins(0, 0);
343 #endif
345 splash(0, true, str(LANG_PLAYLIST_LOAD));
347 if (!buffer)
349 /* use mp3 buffer for maximum load speed */
350 mpeg_stop();
351 talk_buffer_steal(); /* we use the mp3 buffer, need to tell */
353 buffer = mp3buf;
354 buflen = (mp3end - mp3buf);
357 store_index = true;
359 while(1)
361 nread = read(playlist->fd, buffer, buflen);
362 /* Terminate on EOF */
363 if(nread <= 0)
364 break;
366 p = buffer;
368 for(count=0; count < nread; count++,p++) {
370 /* Are we on a new line? */
371 if((*p == '\n') || (*p == '\r'))
373 store_index = true;
375 else if(store_index)
377 store_index = false;
379 if(*p != '#')
381 /* Store a new entry */
382 playlist->indices[ playlist->amount ] = i+count;
383 playlist->amount++;
384 if ( playlist->amount >= playlist->max_playlist_size ) {
385 display_buffer_full();
386 return -1;
392 i+= count;
395 return 0;
399 * Add track to playlist at specified position. There are three special
400 * positions that can be specified:
401 * PLAYLIST_PREPEND - Add track at beginning of playlist
402 * PLAYLIST_INSERT - Add track after current song. NOTE: If there
403 * are already inserted tracks then track is added
404 * to the end of the insertion list.
405 * PLAYLIST_INSERT_FIRST - Add track immediately after current song, no
406 * matter what other tracks have been inserted.
407 * PLAYLIST_INSERT_LAST - Add track to end of playlist
409 static int add_track_to_playlist(struct playlist_info* playlist,
410 const char *filename, int position,
411 bool queue, int seek_pos)
413 int insert_position = position;
414 unsigned int flags = PLAYLIST_INSERT_TYPE_INSERT;
415 int i;
417 if (playlist->amount >= playlist->max_playlist_size)
419 display_buffer_full();
420 return -1;
423 switch (position)
425 case PLAYLIST_PREPEND:
426 insert_position = playlist->first_index;
427 flags = PLAYLIST_INSERT_TYPE_PREPEND;
428 break;
429 case PLAYLIST_INSERT:
430 /* if there are already inserted tracks then add track to end of
431 insertion list else add after current playing track */
432 if (playlist->last_insert_pos >= 0 &&
433 playlist->last_insert_pos < playlist->amount &&
434 (playlist->indices[playlist->last_insert_pos]&
435 PLAYLIST_INSERT_TYPE_MASK) == PLAYLIST_INSERT_TYPE_INSERT)
436 position = insert_position = playlist->last_insert_pos+1;
437 else if (playlist->amount > 0)
438 position = insert_position = playlist->index + 1;
439 else
440 position = insert_position = 0;
442 playlist->last_insert_pos = position;
443 break;
444 case PLAYLIST_INSERT_FIRST:
445 if (playlist->amount > 0)
446 position = insert_position = playlist->index + 1;
447 else
448 position = insert_position = 0;
450 if (playlist->last_insert_pos < 0)
451 playlist->last_insert_pos = position;
452 break;
453 case PLAYLIST_INSERT_LAST:
454 if (playlist->first_index > 0)
455 insert_position = playlist->first_index;
456 else
457 insert_position = playlist->amount;
459 flags = PLAYLIST_INSERT_TYPE_APPEND;
460 break;
463 if (queue)
464 flags |= PLAYLIST_QUEUED;
466 /* shift indices so that track can be added */
467 for (i=playlist->amount; i>insert_position; i--)
468 playlist->indices[i] = playlist->indices[i-1];
470 /* update stored indices if needed */
471 if (playlist->amount > 0 && insert_position <= playlist->index)
472 playlist->index++;
474 if (playlist->amount > 0 && insert_position <= playlist->first_index &&
475 position != PLAYLIST_PREPEND)
477 playlist->first_index++;
479 if (seek_pos < 0 && playlist->current)
481 global_settings.resume_first_index = playlist->first_index;
482 settings_save();
486 if (insert_position < playlist->last_insert_pos ||
487 (insert_position == playlist->last_insert_pos && position < 0))
488 playlist->last_insert_pos++;
490 if (seek_pos < 0 && playlist->control_fd >= 0)
492 int result = -1;
494 if (flush_pending_control(playlist) < 0)
495 return -1;
497 mutex_lock(&playlist->control_mutex);
499 if (lseek(playlist->control_fd, 0, SEEK_END) >= 0)
501 if (fprintf(playlist->control_fd, "%c:%d:%d:", (queue?'Q':'A'),
502 position, playlist->last_insert_pos) > 0)
504 /* save the position in file where track name is written */
505 seek_pos = lseek(playlist->control_fd, 0, SEEK_CUR);
507 if (fprintf(playlist->control_fd, "%s\n", filename) > 0)
508 result = 0;
512 mutex_unlock(&playlist->control_mutex);
514 if (result < 0)
516 splash(HZ*2, true, str(LANG_PLAYLIST_CONTROL_UPDATE_ERROR));
517 return result;
521 playlist->indices[insert_position] = flags | seek_pos;
523 playlist->amount++;
524 playlist->num_inserted_tracks++;
526 return insert_position;
530 * Insert directory into playlist. May be called recursively.
532 static int add_directory_to_playlist(struct playlist_info* playlist,
533 const char *dirname, int *position, bool queue,
534 int *count, bool recurse)
536 char buf[MAX_PATH+1];
537 char *count_str;
538 int result = 0;
539 int num_files = 0;
540 int i;
541 int dirfilter = global_settings.dirfilter;
542 struct entry *files;
543 struct tree_context* tc = tree_get_context();
545 /* use the tree browser dircache to load files */
546 global_settings.dirfilter = SHOW_ALL;
547 num_files = ft_load(tc, dirname);
548 files = (struct entry*) tc->dircache;
550 if(!num_files)
552 splash(HZ*2, true, str(LANG_PLAYLIST_DIRECTORY_ACCESS_ERROR));
553 return 0;
556 /* we've overwritten the dircache so tree browser will need to be
557 reloaded */
558 reload_directory();
560 if (queue)
561 count_str = str(LANG_PLAYLIST_QUEUE_COUNT);
562 else
563 count_str = str(LANG_PLAYLIST_INSERT_COUNT);
565 for (i=0; i<num_files; i++)
567 /* user abort */
568 if (button_get(false) == SETTINGS_CANCEL)
570 result = -1;
571 break;
574 if (files[i].attr & ATTR_DIRECTORY)
576 if (recurse)
578 /* recursively add directories */
579 snprintf(buf, sizeof(buf), "%s/%s", dirname, files[i].name);
580 result = add_directory_to_playlist(playlist, buf, position,
581 queue, count, recurse);
582 if (result < 0)
583 break;
585 /* we now need to reload our current directory */
586 num_files = ft_load(tc, dirname);
587 files = (struct entry*) tc->dircache;
588 if (!num_files)
590 result = -1;
591 break;
594 else
595 continue;
597 else if ((files[i].attr & TREE_ATTR_MASK) == TREE_ATTR_MPA)
599 int insert_pos;
601 snprintf(buf, sizeof(buf), "%s/%s", dirname, files[i].name);
603 insert_pos = add_track_to_playlist(playlist, buf, *position,
604 queue, -1);
605 if (insert_pos < 0)
607 result = -1;
608 break;
611 (*count)++;
613 /* Make sure tracks are inserted in correct order if user requests
614 INSERT_FIRST */
615 if (*position == PLAYLIST_INSERT_FIRST || *position >= 0)
616 *position = insert_pos + 1;
618 if ((*count%PLAYLIST_DISPLAY_COUNT) == 0)
620 display_playlist_count(*count, count_str);
622 if (*count == PLAYLIST_DISPLAY_COUNT)
623 mpeg_flush_and_reload_tracks();
626 /* let the other threads work */
627 yield();
631 /* restore dirfilter */
632 global_settings.dirfilter = dirfilter;
634 return result;
638 * remove track at specified position
640 static int remove_track_from_playlist(struct playlist_info* playlist,
641 int position, bool write)
643 int i;
644 bool inserted;
646 if (playlist->amount <= 0)
647 return -1;
649 inserted = playlist->indices[position] & PLAYLIST_INSERT_TYPE_MASK;
651 /* shift indices now that track has been removed */
652 for (i=position; i<playlist->amount; i++)
653 playlist->indices[i] = playlist->indices[i+1];
655 playlist->amount--;
657 if (inserted)
658 playlist->num_inserted_tracks--;
659 else
660 playlist->deleted = true;
662 /* update stored indices if needed */
663 if (position < playlist->index)
664 playlist->index--;
666 if (position < playlist->first_index)
668 playlist->first_index--;
670 if (write)
672 global_settings.resume_first_index = playlist->first_index;
673 settings_save();
677 if (position <= playlist->last_insert_pos)
678 playlist->last_insert_pos--;
680 if (write && playlist->control_fd >= 0)
682 int result = -1;
684 if (flush_pending_control(playlist) < 0)
685 return -1;
687 mutex_lock(&playlist->control_mutex);
689 if (lseek(playlist->control_fd, 0, SEEK_END) >= 0)
691 if (fprintf(playlist->control_fd, "D:%d\n", position) > 0)
693 fsync(playlist->control_fd);
694 result = 0;
698 mutex_unlock(&playlist->control_mutex);
700 if (result < 0)
702 splash(HZ*2, true, str(LANG_PLAYLIST_CONTROL_UPDATE_ERROR));
703 return result;
707 return 0;
711 * randomly rearrange the array of indices for the playlist. If start_current
712 * is true then update the index to the new index of the current playing track
714 static int randomise_playlist(struct playlist_info* playlist,
715 unsigned int seed, bool start_current,
716 bool write)
718 int count;
719 int candidate;
720 int store;
721 unsigned int current = playlist->indices[playlist->index];
723 /* seed 0 is used to identify sorted playlist for resume purposes */
724 if (seed == 0)
725 seed = 1;
727 /* seed with the given seed */
728 srand(seed);
730 /* randomise entire indices list */
731 for(count = playlist->amount - 1; count >= 0; count--)
733 /* the rand is from 0 to RAND_MAX, so adjust to our value range */
734 candidate = rand() % (count + 1);
736 /* now swap the values at the 'count' and 'candidate' positions */
737 store = playlist->indices[candidate];
738 playlist->indices[candidate] = playlist->indices[count];
739 playlist->indices[count] = store;
742 if (start_current)
743 find_and_set_playlist_index(playlist, current);
745 /* indices have been moved so last insert position is no longer valid */
746 playlist->last_insert_pos = -1;
748 playlist->seed = seed;
749 if (playlist->num_inserted_tracks > 0 || playlist->deleted)
750 playlist->shuffle_modified = true;
752 if (write)
754 /* Don't write to disk immediately. Instead, save in settings and
755 only flush if playlist is modified (insertion/deletion) */
756 playlist->shuffle_flush = true;
757 global_settings.resume_seed = seed;
758 settings_save();
761 return 0;
765 * Sort the array of indices for the playlist. If start_current is true then
766 * set the index to the new index of the current song.
768 static int sort_playlist(struct playlist_info* playlist, bool start_current,
769 bool write)
771 unsigned int current = playlist->indices[playlist->index];
773 if (playlist->amount > 0)
774 qsort(playlist->indices, playlist->amount, sizeof(playlist->indices[0]),
775 compare);
777 if (start_current)
778 find_and_set_playlist_index(playlist, current);
780 /* indices have been moved so last insert position is no longer valid */
781 playlist->last_insert_pos = -1;
783 if (!playlist->num_inserted_tracks && !playlist->deleted)
784 playlist->shuffle_modified = false;
785 if (write && playlist->control_fd >= 0)
787 /* Don't write to disk immediately. Instead, save in settings and
788 only flush if playlist is modified (insertion/deletion) */
789 playlist->shuffle_flush = true;
790 global_settings.resume_seed = 0;
791 settings_save();
794 return 0;
798 * returns the index of the track that is "steps" away from current playing
799 * track.
801 static int get_next_index(const struct playlist_info* playlist, int steps)
803 int current_index = playlist->index;
804 int next_index = -1;
806 if (playlist->amount <= 0)
807 return -1;
809 switch (global_settings.repeat_mode)
811 case REPEAT_OFF:
813 current_index = rotate_index(playlist, current_index);
815 next_index = current_index+steps;
816 if ((next_index < 0) || (next_index >= playlist->amount))
817 next_index = -1;
818 else
819 next_index = (next_index+playlist->first_index) %
820 playlist->amount;
822 break;
825 case REPEAT_ONE:
826 next_index = current_index;
827 break;
829 case REPEAT_ALL:
830 default:
832 next_index = (current_index+steps) % playlist->amount;
833 while (next_index < 0)
834 next_index += playlist->amount;
836 if (steps >= playlist->amount)
838 int i, index;
840 index = next_index;
841 next_index = -1;
843 /* second time around so skip the queued files */
844 for (i=0; i<playlist->amount; i++)
846 if (playlist->indices[index] & PLAYLIST_QUEUE_MASK)
847 index = (index+1) % playlist->amount;
848 else
850 next_index = index;
851 break;
855 break;
859 return next_index;
863 * Search for the seek track and set appropriate indices. Used after shuffle
864 * to make sure the current index is still pointing to correct track.
866 static void find_and_set_playlist_index(struct playlist_info* playlist,
867 unsigned int seek)
869 int i;
871 /* Set the index to the current song */
872 for (i=0; i<playlist->amount; i++)
874 if (playlist->indices[i] == seek)
876 playlist->index = playlist->first_index = i;
878 if (playlist->current)
880 global_settings.resume_first_index = i;
881 settings_save();
884 break;
890 * used to sort track indices. Sort order is as follows:
891 * 1. Prepended tracks (in prepend order)
892 * 2. Playlist/directory tracks (in playlist order)
893 * 3. Inserted/Appended tracks (in insert order)
895 static int compare(const void* p1, const void* p2)
897 unsigned int* e1 = (unsigned int*) p1;
898 unsigned int* e2 = (unsigned int*) p2;
899 unsigned int flags1 = *e1 & PLAYLIST_INSERT_TYPE_MASK;
900 unsigned int flags2 = *e2 & PLAYLIST_INSERT_TYPE_MASK;
902 if (flags1 == flags2)
903 return (*e1 & PLAYLIST_SEEK_MASK) - (*e2 & PLAYLIST_SEEK_MASK);
904 else if (flags1 == PLAYLIST_INSERT_TYPE_PREPEND ||
905 flags2 == PLAYLIST_INSERT_TYPE_APPEND)
906 return -1;
907 else if (flags1 == PLAYLIST_INSERT_TYPE_APPEND ||
908 flags2 == PLAYLIST_INSERT_TYPE_PREPEND)
909 return 1;
910 else if (flags1 && flags2)
911 return (*e1 & PLAYLIST_SEEK_MASK) - (*e2 & PLAYLIST_SEEK_MASK);
912 else
913 return *e1 - *e2;
917 * gets pathname for track at seek index
919 static int get_filename(struct playlist_info* playlist, int seek,
920 bool control_file, char *buf, int buf_length)
922 int fd;
923 int max = -1;
924 char tmp_buf[MAX_PATH+1];
925 char dir_buf[MAX_PATH+1];
927 if (buf_length > MAX_PATH+1)
928 buf_length = MAX_PATH+1;
930 if (playlist->in_ram && !control_file)
932 strncpy(tmp_buf, &playlist->buffer[seek], sizeof(tmp_buf));
933 tmp_buf[MAX_PATH] = '\0';
934 max = strlen(tmp_buf) + 1;
936 else
938 if (control_file)
939 fd = playlist->control_fd;
940 else
942 if(-1 == playlist->fd)
943 playlist->fd = open(playlist->filename, O_RDONLY);
945 fd = playlist->fd;
948 if(-1 != fd)
950 if (control_file)
951 mutex_lock(&playlist->control_mutex);
953 lseek(fd, seek, SEEK_SET);
954 max = read(fd, tmp_buf, buf_length);
956 if (control_file)
957 mutex_unlock(&playlist->control_mutex);
960 if (max < 0)
962 if (control_file)
963 splash(HZ*2, true,
964 str(LANG_PLAYLIST_CONTROL_ACCESS_ERROR));
965 else
966 splash(HZ*2, true, str(LANG_PLAYLIST_ACCESS_ERROR));
968 return max;
972 strncpy(dir_buf, playlist->filename, playlist->dirlen-1);
973 dir_buf[playlist->dirlen-1] = 0;
975 return (format_track_path(buf, tmp_buf, buf_length, max, dir_buf));
979 * Returns absolute path of track
981 static int format_track_path(char *dest, char *src, int buf_length, int max,
982 char *dir)
984 int i = 0;
985 int j;
986 char *temp_ptr;
988 /* Zero-terminate the file name */
989 while((src[i] != '\n') &&
990 (src[i] != '\r') &&
991 (i < max))
992 i++;
994 /* Now work back killing white space */
995 while((src[i-1] == ' ') ||
996 (src[i-1] == '\t'))
997 i--;
999 src[i]=0;
1001 /* replace backslashes with forward slashes */
1002 for ( j=0; j<i; j++ )
1003 if ( src[j] == '\\' )
1004 src[j] = '/';
1006 if('/' == src[0])
1008 strncpy(dest, src, buf_length);
1010 else
1012 /* handle dos style drive letter */
1013 if (':' == src[1])
1014 strncpy(dest, &src[2], buf_length);
1015 else if (!strncmp(src, "../", 3))
1017 /* handle relative paths */
1018 i=3;
1019 while(!strncmp(&src[i], "../", 3))
1020 i += 3;
1021 for (j=0; j<i/3; j++) {
1022 temp_ptr = strrchr(dir, '/');
1023 if (temp_ptr)
1024 *temp_ptr = '\0';
1025 else
1026 break;
1028 snprintf(dest, buf_length, "%s/%s", dir, &src[i]);
1030 else if ( '.' == src[0] && '/' == src[1] ) {
1031 snprintf(dest, buf_length, "%s/%s", dir, &src[2]);
1033 else {
1034 snprintf(dest, buf_length, "%s/%s", dir, src);
1038 return 0;
1042 * Display splash message showing progress of playlist/directory insertion or
1043 * save.
1045 static void display_playlist_count(int count, const char *fmt)
1047 lcd_clear_display();
1049 #ifdef HAVE_LCD_BITMAP
1050 if(global_settings.statusbar)
1051 lcd_setmargins(0, STATUSBAR_HEIGHT);
1052 else
1053 lcd_setmargins(0, 0);
1054 #endif
1056 splash(0, true, fmt, count,
1057 #if CONFIG_KEYPAD == PLAYER_PAD
1058 str(LANG_STOP_ABORT)
1059 #else
1060 str(LANG_OFF_ABORT)
1061 #endif
1066 * Display buffer full message
1068 static void display_buffer_full(void)
1070 splash(HZ*2, true, "%s %s",
1071 str(LANG_PLAYINDICES_PLAYLIST),
1072 str(LANG_PLAYINDICES_BUFFER));
1076 * Flush any pending control commands to disk. Called when playlist is being
1077 * modified. Returns 0 on success and -1 on failure.
1079 static int flush_pending_control(struct playlist_info* playlist)
1081 int result = 0;
1083 if (playlist->shuffle_flush && global_settings.resume_seed >= 0)
1085 /* pending shuffle */
1086 mutex_lock(&playlist->control_mutex);
1088 if (lseek(playlist->control_fd, 0, SEEK_END) >= 0)
1090 if (global_settings.resume_seed == 0)
1091 result = fprintf(playlist->control_fd, "U:%d\n",
1092 playlist->first_index);
1093 else
1094 result = fprintf(playlist->control_fd, "S:%d:%d\n",
1095 global_settings.resume_seed, playlist->first_index);
1097 if (result > 0)
1099 fsync(playlist->control_fd);
1101 playlist->shuffle_flush = false;
1102 global_settings.resume_seed = -1;
1103 settings_save();
1105 result = 0;
1107 else
1108 result = -1;
1110 else
1111 result = -1;
1113 mutex_unlock(&playlist->control_mutex);
1115 if (result < 0)
1117 splash(HZ*2, true, str(LANG_PLAYLIST_CONTROL_UPDATE_ERROR));
1118 return result;
1122 return result;
1126 * Rotate indices such that first_index is index 0
1128 static int rotate_index(const struct playlist_info* playlist, int index)
1130 index -= playlist->first_index;
1131 if (index < 0)
1132 index += playlist->amount;
1134 return index;
1138 * Initialize playlist entries at startup
1140 void playlist_init(void)
1142 struct playlist_info* playlist = &current_playlist;
1144 playlist->current = true;
1145 snprintf(playlist->control_filename, sizeof(playlist->control_filename),
1146 "%s", PLAYLIST_CONTROL_FILE);
1147 playlist->fd = -1;
1148 playlist->control_fd = -1;
1149 playlist->max_playlist_size = global_settings.max_files_in_playlist;
1150 playlist->indices = buffer_alloc(playlist->max_playlist_size * sizeof(int));
1151 playlist->buffer_size =
1152 AVERAGE_FILENAME_LENGTH * global_settings.max_files_in_dir;
1153 playlist->buffer = buffer_alloc(playlist->buffer_size);
1154 mutex_init(&playlist->control_mutex);
1155 empty_playlist(playlist, true);
1159 * Create new playlist
1161 int playlist_create(const char *dir, const char *file)
1163 struct playlist_info* playlist = &current_playlist;
1165 new_playlist(playlist, dir, file);
1167 if (file)
1168 /* load the playlist file */
1169 add_indices_to_playlist(playlist, NULL, 0);
1171 return 0;
1174 #define PLAYLIST_COMMAND_SIZE (MAX_PATH+12)
1177 * Restore the playlist state based on control file commands. Called to
1178 * resume playback after shutdown.
1180 int playlist_resume(void)
1182 struct playlist_info* playlist = &current_playlist;
1183 char *buffer;
1184 int buflen;
1185 int nread;
1186 int total_read = 0;
1187 int control_file_size = 0;
1188 bool first = true;
1189 bool sorted = true;
1191 enum {
1192 resume_playlist,
1193 resume_add,
1194 resume_queue,
1195 resume_delete,
1196 resume_shuffle,
1197 resume_unshuffle,
1198 resume_reset,
1199 resume_comment
1202 /* use mp3 buffer for maximum load speed */
1203 talk_buffer_steal(); /* we use the mp3 buffer, need to tell */
1204 buflen = (mp3end - mp3buf);
1205 buffer = mp3buf;
1207 empty_playlist(playlist, true);
1209 playlist->control_fd = open(playlist->control_filename, O_RDWR);
1210 if (playlist->control_fd < 0)
1212 splash(HZ*2, true, str(LANG_PLAYLIST_CONTROL_ACCESS_ERROR));
1213 return -1;
1215 playlist->control_created = true;
1217 control_file_size = filesize(playlist->control_fd);
1218 if (control_file_size <= 0)
1220 splash(HZ*2, true, str(LANG_PLAYLIST_CONTROL_ACCESS_ERROR));
1221 return -1;
1224 /* read a small amount first to get the header */
1225 nread = read(playlist->control_fd, buffer,
1226 PLAYLIST_COMMAND_SIZE<buflen?PLAYLIST_COMMAND_SIZE:buflen);
1227 if(nread <= 0)
1229 splash(HZ*2, true, str(LANG_PLAYLIST_CONTROL_ACCESS_ERROR));
1230 return -1;
1233 while (1)
1235 int result = 0;
1236 int count;
1237 int current_command = resume_comment;
1238 int last_newline = 0;
1239 int str_count = -1;
1240 bool newline = true;
1241 bool exit_loop = false;
1242 char *p = buffer;
1243 char *str1 = NULL;
1244 char *str2 = NULL;
1245 char *str3 = NULL;
1247 for(count=0; count<nread && !exit_loop; count++,p++)
1249 /* Are we on a new line? */
1250 if((*p == '\n') || (*p == '\r'))
1252 *p = '\0';
1254 /* save last_newline in case we need to load more data */
1255 last_newline = count;
1257 switch (current_command)
1259 case resume_playlist:
1261 /* str1=version str2=dir str3=file */
1262 int version;
1264 if (!str1)
1266 result = -1;
1267 exit_loop = true;
1268 break;
1271 if (!str2)
1272 str2 = "";
1274 if (!str3)
1275 str3 = "";
1277 version = atoi(str1);
1279 if (version != PLAYLIST_CONTROL_FILE_VERSION)
1280 return -1;
1282 update_playlist_filename(playlist, str2, str3);
1284 if (str3[0] != '\0')
1286 /* NOTE: add_indices_to_playlist() overwrites the
1287 mp3buf so we need to reload control file
1288 data */
1289 add_indices_to_playlist(playlist, NULL, 0);
1291 else if (str2[0] != '\0')
1293 playlist->in_ram = true;
1294 resume_directory(str2);
1297 /* load the rest of the data */
1298 first = false;
1299 exit_loop = true;
1301 break;
1303 case resume_add:
1304 case resume_queue:
1306 /* str1=position str2=last_position str3=file */
1307 int position, last_position;
1308 bool queue;
1310 if (!str1 || !str2 || !str3)
1312 result = -1;
1313 exit_loop = true;
1314 break;
1317 position = atoi(str1);
1318 last_position = atoi(str2);
1320 queue = (current_command == resume_add)?false:true;
1322 /* seek position is based on str3's position in
1323 buffer */
1324 if (add_track_to_playlist(playlist, str3, position,
1325 queue, total_read+(str3-buffer)) < 0)
1326 return -1;
1328 playlist->last_insert_pos = last_position;
1330 break;
1332 case resume_delete:
1334 /* str1=position */
1335 int position;
1337 if (!str1)
1339 result = -1;
1340 exit_loop = true;
1341 break;
1344 position = atoi(str1);
1346 if (remove_track_from_playlist(playlist, position,
1347 false) < 0)
1348 return -1;
1350 break;
1352 case resume_shuffle:
1354 /* str1=seed str2=first_index */
1355 int seed;
1357 if (!str1 || !str2)
1359 result = -1;
1360 exit_loop = true;
1361 break;
1364 if (!sorted)
1366 /* Always sort list before shuffling */
1367 sort_playlist(playlist, false, false);
1370 seed = atoi(str1);
1371 playlist->first_index = atoi(str2);
1373 if (randomise_playlist(playlist, seed, false,
1374 false) < 0)
1375 return -1;
1377 sorted = false;
1378 break;
1380 case resume_unshuffle:
1382 /* str1=first_index */
1383 if (!str1)
1385 result = -1;
1386 exit_loop = true;
1387 break;
1390 playlist->first_index = atoi(str1);
1392 if (sort_playlist(playlist, false, false) < 0)
1393 return -1;
1395 sorted = true;
1396 break;
1398 case resume_reset:
1400 playlist->last_insert_pos = -1;
1401 break;
1403 case resume_comment:
1404 default:
1405 break;
1408 newline = true;
1410 /* to ignore any extra newlines */
1411 current_command = resume_comment;
1413 else if(newline)
1415 newline = false;
1417 /* first non-comment line must always specify playlist */
1418 if (first && *p != 'P' && *p != '#')
1420 result = -1;
1421 exit_loop = true;
1422 break;
1425 switch (*p)
1427 case 'P':
1428 /* playlist can only be specified once */
1429 if (!first)
1431 result = -1;
1432 exit_loop = true;
1433 break;
1436 current_command = resume_playlist;
1437 break;
1438 case 'A':
1439 current_command = resume_add;
1440 break;
1441 case 'Q':
1442 current_command = resume_queue;
1443 break;
1444 case 'D':
1445 current_command = resume_delete;
1446 break;
1447 case 'S':
1448 current_command = resume_shuffle;
1449 break;
1450 case 'U':
1451 current_command = resume_unshuffle;
1452 break;
1453 case 'R':
1454 current_command = resume_reset;
1455 break;
1456 case '#':
1457 current_command = resume_comment;
1458 break;
1459 default:
1460 result = -1;
1461 exit_loop = true;
1462 break;
1465 str_count = -1;
1466 str1 = NULL;
1467 str2 = NULL;
1468 str3 = NULL;
1470 else if(current_command != resume_comment)
1472 /* all control file strings are separated with a colon.
1473 Replace the colon with 0 to get proper strings that can be
1474 used by commands above */
1475 if (*p == ':')
1477 *p = '\0';
1478 str_count++;
1480 if ((count+1) < nread)
1482 switch (str_count)
1484 case 0:
1485 str1 = p+1;
1486 break;
1487 case 1:
1488 str2 = p+1;
1489 break;
1490 case 2:
1491 str3 = p+1;
1492 break;
1493 default:
1494 /* allow last string to contain colons */
1495 *p = ':';
1496 break;
1503 if (result < 0)
1505 splash(HZ*2, true, str(LANG_PLAYLIST_CONTROL_INVALID));
1506 return result;
1509 if (!newline || (exit_loop && count<nread))
1511 if ((total_read + count) >= control_file_size)
1513 /* no newline at end of control file */
1514 splash(HZ*2, true, str(LANG_PLAYLIST_CONTROL_INVALID));
1515 return -1;
1518 /* We didn't end on a newline or we exited loop prematurely.
1519 Either way, re-read the remainder. */
1520 count = last_newline;
1521 lseek(playlist->control_fd, total_read+count, SEEK_SET);
1524 total_read += count;
1526 if (first)
1527 /* still looking for header */
1528 nread = read(playlist->control_fd, buffer,
1529 PLAYLIST_COMMAND_SIZE<buflen?PLAYLIST_COMMAND_SIZE:buflen);
1530 else
1531 nread = read(playlist->control_fd, buffer, buflen);
1533 /* Terminate on EOF */
1534 if(nread <= 0)
1536 if (global_settings.resume_seed >= 0)
1538 /* Apply shuffle command saved in settings */
1539 if (global_settings.resume_seed == 0)
1540 sort_playlist(playlist, false, true);
1541 else
1543 if (!sorted)
1544 sort_playlist(playlist, false, false);
1546 randomise_playlist(playlist, global_settings.resume_seed,
1547 false, true);
1550 playlist->first_index = global_settings.resume_first_index;
1553 break;
1557 return 0;
1561 * Add track to in_ram playlist. Used when playing directories.
1563 int playlist_add(const char *filename)
1565 struct playlist_info* playlist = &current_playlist;
1566 int len = strlen(filename);
1568 if((len+1 > playlist->buffer_size - playlist->buffer_end_pos) ||
1569 (playlist->amount >= playlist->max_playlist_size))
1571 display_buffer_full();
1572 return -1;
1575 playlist->indices[playlist->amount++] = playlist->buffer_end_pos;
1577 strcpy(&playlist->buffer[playlist->buffer_end_pos], filename);
1578 playlist->buffer_end_pos += len;
1579 playlist->buffer[playlist->buffer_end_pos++] = '\0';
1581 return 0;
1584 /* shuffle newly created playlist using random seed. */
1585 int playlist_shuffle(int random_seed, int start_index)
1587 struct playlist_info* playlist = &current_playlist;
1589 unsigned int seek_pos = 0;
1590 bool start_current = false;
1592 if (start_index >= 0 && global_settings.play_selected)
1594 /* store the seek position before the shuffle */
1595 seek_pos = playlist->indices[start_index];
1596 playlist->index = global_settings.resume_first_index =
1597 playlist->first_index = start_index;
1598 start_current = true;
1601 splash(0, true, str(LANG_PLAYLIST_SHUFFLE));
1603 randomise_playlist(playlist, random_seed, start_current, true);
1605 /* Flush shuffle command to disk */
1606 flush_pending_control(playlist);
1608 return playlist->index;
1611 /* start playing current playlist at specified index/offset */
1612 int playlist_start(int start_index, int offset)
1614 struct playlist_info* playlist = &current_playlist;
1616 playlist->index = start_index;
1617 talk_buffer_steal(); /* will use the mp3 buffer */
1618 mpeg_play(offset);
1620 return 0;
1623 /* Returns false if 'steps' is out of bounds, else true */
1624 bool playlist_check(int steps)
1626 struct playlist_info* playlist = &current_playlist;
1627 int index = get_next_index(playlist, steps);
1628 return (index >= 0);
1631 /* get trackname of track that is "steps" away from current playing track.
1632 NULL is used to identify end of playlist */
1633 char* playlist_peek(int steps)
1635 struct playlist_info* playlist = &current_playlist;
1636 int seek;
1637 int fd;
1638 char *temp_ptr;
1639 int index;
1640 bool control_file;
1642 index = get_next_index(playlist, steps);
1643 if (index < 0)
1644 return NULL;
1646 control_file = playlist->indices[index] & PLAYLIST_INSERT_TYPE_MASK;
1647 seek = playlist->indices[index] & PLAYLIST_SEEK_MASK;
1649 if (get_filename(playlist, seek, control_file, now_playing,
1650 MAX_PATH+1) < 0)
1651 return NULL;
1653 temp_ptr = now_playing;
1655 if (!playlist->in_ram || control_file)
1657 /* remove bogus dirs from beginning of path
1658 (workaround for buggy playlist creation tools) */
1659 while (temp_ptr)
1661 fd = open(temp_ptr, O_RDONLY);
1662 if (fd >= 0)
1664 close(fd);
1665 break;
1668 temp_ptr = strchr(temp_ptr+1, '/');
1671 if (!temp_ptr)
1673 /* Even though this is an invalid file, we still need to pass a
1674 file name to the caller because NULL is used to indicate end
1675 of playlist */
1676 return now_playing;
1680 return temp_ptr;
1684 * Update indices as track has changed
1686 int playlist_next(int steps)
1688 struct playlist_info* playlist = &current_playlist;
1689 int index;
1691 if (steps > 0 && global_settings.repeat_mode != REPEAT_ONE)
1693 int i, j;
1695 /* We need to delete all the queued songs */
1696 for (i=0, j=steps; i<j; i++)
1698 index = get_next_index(playlist, i);
1700 if (playlist->indices[index] & PLAYLIST_QUEUE_MASK)
1702 remove_track_from_playlist(playlist, index, true);
1703 steps--; /* one less track */
1708 index = get_next_index(playlist, steps);
1709 playlist->index = index;
1711 if (playlist->last_insert_pos >= 0 && steps > 0)
1713 /* check to see if we've gone beyond the last inserted track */
1714 int cur = rotate_index(playlist, index);
1715 int last_pos = rotate_index(playlist, playlist->last_insert_pos);
1717 if (cur > last_pos)
1719 /* reset last inserted track */
1720 playlist->last_insert_pos = -1;
1722 if (playlist->control_fd >= 0)
1724 int result = -1;
1726 mutex_lock(&playlist->control_mutex);
1728 if (lseek(playlist->control_fd, 0, SEEK_END) >= 0)
1730 if (fprintf(playlist->control_fd, "R\n") > 0)
1732 fsync(playlist->control_fd);
1733 result = 0;
1737 mutex_unlock(&playlist->control_mutex);
1739 if (result < 0)
1741 splash(HZ*2, true,
1742 str(LANG_PLAYLIST_CONTROL_UPDATE_ERROR));
1743 return result;
1749 return index;
1752 /* Get resume info for current playing song. If return value is -1 then
1753 settings shouldn't be saved. */
1754 int playlist_get_resume_info(int *resume_index)
1756 struct playlist_info* playlist = &current_playlist;
1758 *resume_index = playlist->index;
1760 return 0;
1763 /* Returns index of current playing track for display purposes. This value
1764 should not be used for resume purposes as it doesn't represent the actual
1765 index into the playlist */
1766 int playlist_get_display_index(void)
1768 struct playlist_info* playlist = &current_playlist;
1770 /* first_index should always be index 0 for display purposes */
1771 int index = rotate_index(playlist, playlist->index);
1773 return (index+1);
1776 /* returns number of tracks in current playlist */
1777 int playlist_amount(void)
1779 return playlist_amount_ex(NULL);
1783 * Create a new playlist If playlist is not NULL then we're loading a
1784 * playlist off disk for viewing/editing. The index_buffer is used to store
1785 * playlist indices (required for and only used if !current playlist). The
1786 * temp_buffer (if not NULL) is used as a scratchpad when loading indices.
1788 int playlist_create_ex(struct playlist_info* playlist,
1789 const char* dir, const char* file,
1790 void* index_buffer, int index_buffer_size,
1791 void* temp_buffer, int temp_buffer_size)
1793 if (!playlist)
1794 playlist = &current_playlist;
1795 else
1797 /* Initialize playlist structure */
1798 int r = rand() % 10;
1799 playlist->current = false;
1801 /* Use random name for control file */
1802 snprintf(playlist->control_filename, sizeof(playlist->control_filename),
1803 "%s.%d", PLAYLIST_CONTROL_FILE, r);
1804 playlist->fd = -1;
1805 playlist->control_fd = -1;
1807 if (index_buffer)
1809 int num_indices = index_buffer_size / sizeof(int);
1811 if (num_indices > global_settings.max_files_in_playlist)
1812 num_indices = global_settings.max_files_in_playlist;
1814 playlist->max_playlist_size = num_indices;
1815 playlist->indices = index_buffer;
1817 else
1819 playlist->max_playlist_size = current_playlist.max_playlist_size;
1820 playlist->indices = current_playlist.indices;
1823 playlist->buffer_size = 0;
1824 playlist->buffer = NULL;
1825 mutex_init(&playlist->control_mutex);
1828 new_playlist(playlist, dir, file);
1830 if (file)
1831 /* load the playlist file */
1832 add_indices_to_playlist(playlist, temp_buffer, temp_buffer_size);
1834 return 0;
1838 * Set the specified playlist as the current.
1839 * NOTE: You will get undefined behaviour if something is already playing so
1840 * remember to stop before calling this. Also, this call will
1841 * effectively close your playlist, making it unusable.
1843 int playlist_set_current(struct playlist_info* playlist)
1845 if (!playlist || (check_control(playlist) < 0))
1846 return -1;
1848 empty_playlist(&current_playlist, false);
1850 strncpy(current_playlist.filename, playlist->filename,
1851 sizeof(current_playlist.filename));
1853 current_playlist.fd = playlist->fd;
1855 close(playlist->control_fd);
1856 remove(current_playlist.control_filename);
1857 if (rename(playlist->control_filename,
1858 current_playlist.control_filename) < 0)
1859 return -1;
1860 current_playlist.control_fd = open(current_playlist.control_filename,
1861 O_RDWR);
1862 if (current_playlist.control_fd < 0)
1863 return -1;
1864 current_playlist.control_created = true;
1866 current_playlist.dirlen = playlist->dirlen;
1868 if (playlist->indices && playlist->indices != current_playlist.indices)
1869 memcpy(current_playlist.indices, playlist->indices,
1870 playlist->max_playlist_size*sizeof(int));
1872 current_playlist.first_index = playlist->first_index;
1873 current_playlist.amount = playlist->amount;
1874 current_playlist.last_insert_pos = playlist->last_insert_pos;
1875 current_playlist.seed = playlist->seed;
1876 current_playlist.shuffle_modified = playlist->shuffle_modified;
1877 current_playlist.deleted = playlist->deleted;
1878 current_playlist.num_inserted_tracks = playlist->num_inserted_tracks;
1879 current_playlist.shuffle_flush = playlist->shuffle_flush;
1881 return 0;
1885 * Close files and delete control file for non-current playlist.
1887 void playlist_close(struct playlist_info* playlist)
1889 if (!playlist)
1890 return;
1892 if (playlist->fd >= 0)
1893 close(playlist->fd);
1895 if (playlist->control_fd >= 0)
1896 close(playlist->control_fd);
1898 if (playlist->control_created)
1899 remove(playlist->control_filename);
1903 * Insert track into playlist at specified position (or one of the special
1904 * positions). Returns position where track was inserted or -1 if error.
1906 int playlist_insert_track(struct playlist_info* playlist, const char *filename,
1907 int position, bool queue)
1909 int result;
1911 if (!playlist)
1912 playlist = &current_playlist;
1914 if (check_control(playlist) < 0)
1916 splash(HZ*2, true, str(LANG_PLAYLIST_CONTROL_ACCESS_ERROR));
1917 return -1;
1920 result = add_track_to_playlist(playlist, filename, position, queue, -1);
1922 if (result != -1)
1924 fsync(playlist->control_fd);
1925 mpeg_flush_and_reload_tracks();
1928 return result;
1932 * Insert all tracks from specified directory into playlist.
1934 int playlist_insert_directory(struct playlist_info* playlist,
1935 const char *dirname, int position, bool queue,
1936 bool recurse)
1938 int count = 0;
1939 int result;
1940 char *count_str;
1942 if (!playlist)
1943 playlist = &current_playlist;
1945 if (check_control(playlist) < 0)
1947 splash(HZ*2, true, str(LANG_PLAYLIST_CONTROL_ACCESS_ERROR));
1948 return -1;
1951 if (queue)
1952 count_str = str(LANG_PLAYLIST_QUEUE_COUNT);
1953 else
1954 count_str = str(LANG_PLAYLIST_INSERT_COUNT);
1956 display_playlist_count(count, count_str);
1958 result = add_directory_to_playlist(playlist, dirname, &position, queue,
1959 &count, recurse);
1960 fsync(playlist->control_fd);
1962 display_playlist_count(count, count_str);
1963 mpeg_flush_and_reload_tracks();
1965 return result;
1969 * Insert all tracks from specified playlist into dynamic playlist.
1971 int playlist_insert_playlist(struct playlist_info* playlist, char *filename,
1972 int position, bool queue)
1974 int fd;
1975 int max;
1976 char *temp_ptr;
1977 char *dir;
1978 char *count_str;
1979 char temp_buf[MAX_PATH+1];
1980 char trackname[MAX_PATH+1];
1981 int count = 0;
1982 int result = 0;
1984 if (!playlist)
1985 playlist = &current_playlist;
1987 if (check_control(playlist) < 0)
1989 splash(HZ*2, true, str(LANG_PLAYLIST_CONTROL_ACCESS_ERROR));
1990 return -1;
1993 fd = open(filename, O_RDONLY);
1994 if (fd < 0)
1996 splash(HZ*2, true, str(LANG_PLAYLIST_ACCESS_ERROR));
1997 return -1;
2000 /* we need the directory name for formatting purposes */
2001 dir = filename;
2003 temp_ptr = strrchr(filename+1,'/');
2004 if (temp_ptr)
2005 *temp_ptr = 0;
2006 else
2007 dir = "/";
2009 if (queue)
2010 count_str = str(LANG_PLAYLIST_QUEUE_COUNT);
2011 else
2012 count_str = str(LANG_PLAYLIST_INSERT_COUNT);
2014 display_playlist_count(count, count_str);
2016 while ((max = read_line(fd, temp_buf, sizeof(temp_buf))) > 0)
2018 /* user abort */
2019 if (button_get(false) == SETTINGS_CANCEL)
2020 break;
2022 if (temp_buf[0] != '#' && temp_buf[0] != '\0')
2024 int insert_pos;
2026 /* we need to format so that relative paths are correctly
2027 handled */
2028 if (format_track_path(trackname, temp_buf, sizeof(trackname), max,
2029 dir) < 0)
2031 result = -1;
2032 break;
2035 insert_pos = add_track_to_playlist(playlist, trackname, position,
2036 queue, -1);
2038 if (insert_pos < 0)
2040 result = -1;
2041 break;
2044 /* Make sure tracks are inserted in correct order if user
2045 requests INSERT_FIRST */
2046 if (position == PLAYLIST_INSERT_FIRST || position >= 0)
2047 position = insert_pos + 1;
2049 count++;
2051 if ((count%PLAYLIST_DISPLAY_COUNT) == 0)
2053 display_playlist_count(count, count_str);
2055 if (count == PLAYLIST_DISPLAY_COUNT)
2056 mpeg_flush_and_reload_tracks();
2060 /* let the other threads work */
2061 yield();
2064 close(fd);
2065 fsync(playlist->control_fd);
2067 if (temp_ptr)
2068 *temp_ptr = '/';
2070 display_playlist_count(count, count_str);
2071 mpeg_flush_and_reload_tracks();
2073 return result;
2077 * Delete track at specified index. If index is PLAYLIST_DELETE_CURRENT then
2078 * we want to delete the current playing track.
2080 int playlist_delete(struct playlist_info* playlist, int index)
2082 int result = 0;
2084 if (!playlist)
2085 playlist = &current_playlist;
2087 if (check_control(playlist) < 0)
2089 splash(HZ*2, true, str(LANG_PLAYLIST_CONTROL_ACCESS_ERROR));
2090 return -1;
2093 if (index == PLAYLIST_DELETE_CURRENT)
2094 index = playlist->index;
2096 result = remove_track_from_playlist(playlist, index, true);
2098 if (result != -1)
2099 mpeg_flush_and_reload_tracks();
2101 return result;
2105 * Move track at index to new_index. Tracks between the two are shifted
2106 * appropriately. Returns 0 on success and -1 on failure.
2108 int playlist_move(struct playlist_info* playlist, int index, int new_index)
2110 int result;
2111 int seek;
2112 bool control_file;
2113 bool queue;
2114 bool current = false;
2115 int r;
2116 char filename[MAX_PATH];
2118 if (!playlist)
2119 playlist = &current_playlist;
2121 if (check_control(playlist) < 0)
2123 splash(HZ*2, true, str(LANG_PLAYLIST_CONTROL_ACCESS_ERROR));
2124 return -1;
2127 if (index == new_index)
2128 return -1;
2130 if (index == playlist->index)
2131 /* Moving the current track */
2132 current = true;
2134 control_file = playlist->indices[index] & PLAYLIST_INSERT_TYPE_MASK;
2135 queue = playlist->indices[index] & PLAYLIST_QUEUE_MASK;
2136 seek = playlist->indices[index] & PLAYLIST_SEEK_MASK;
2138 if (get_filename(playlist, seek, control_file, filename,
2139 sizeof(filename)) < 0)
2140 return -1;
2142 /* Delete track from original position */
2143 result = remove_track_from_playlist(playlist, index, true);
2145 if (result != -1)
2147 /* We want to insert the track at the position that was specified by
2148 new_index. This may be different then new_index because of the
2149 shifting that occurred after the delete */
2150 r = rotate_index(playlist, new_index);
2152 if (r == 0)
2153 /* First index */
2154 new_index = PLAYLIST_PREPEND;
2155 else if (r == playlist->amount)
2156 /* Append */
2157 new_index = PLAYLIST_INSERT_LAST;
2158 else
2159 /* Calculate index of desired position */
2160 new_index = (r+playlist->first_index)%playlist->amount;
2162 result = add_track_to_playlist(playlist, filename, new_index, queue,
2163 -1);
2165 if (result != -1)
2167 if (current)
2169 /* Moved the current track */
2170 switch (new_index)
2172 case PLAYLIST_PREPEND:
2173 playlist->index = playlist->first_index;
2174 break;
2175 case PLAYLIST_INSERT_LAST:
2176 playlist->index = playlist->first_index - 1;
2177 if (playlist->index < 0)
2178 playlist->index += playlist->amount;
2179 break;
2180 default:
2181 playlist->index = new_index;
2182 break;
2186 fsync(playlist->control_fd);
2187 mpeg_flush_and_reload_tracks();
2191 return result;
2194 /* shuffle currently playing playlist */
2195 int playlist_randomise(struct playlist_info* playlist, unsigned int seed,
2196 bool start_current)
2198 int result;
2200 if (!playlist)
2201 playlist = &current_playlist;
2203 check_control(playlist);
2205 result = randomise_playlist(playlist, seed, start_current, true);
2207 if (result != -1)
2208 mpeg_flush_and_reload_tracks();
2210 return result;
2213 /* sort currently playing playlist */
2214 int playlist_sort(struct playlist_info* playlist, bool start_current)
2216 int result;
2218 if (!playlist)
2219 playlist = &current_playlist;
2221 check_control(playlist);
2223 result = sort_playlist(playlist, start_current, true);
2225 if (result != -1)
2226 mpeg_flush_and_reload_tracks();
2228 return result;
2231 /* returns true if playlist has been modified */
2232 bool playlist_modified(const struct playlist_info* playlist)
2234 if (!playlist)
2235 playlist = &current_playlist;
2237 if (playlist->shuffle_modified ||
2238 playlist->deleted ||
2239 playlist->num_inserted_tracks > 0)
2240 return true;
2242 return false;
2245 /* returns index of first track in playlist */
2246 int playlist_get_first_index(const struct playlist_info* playlist)
2248 if (!playlist)
2249 playlist = &current_playlist;
2251 return playlist->first_index;
2254 /* returns shuffle seed of playlist */
2255 int playlist_get_seed(const struct playlist_info* playlist)
2257 if (!playlist)
2258 playlist = &current_playlist;
2260 return playlist->seed;
2263 /* returns number of tracks in playlist (includes queued/inserted tracks) */
2264 int playlist_amount_ex(const struct playlist_info* playlist)
2266 if (!playlist)
2267 playlist = &current_playlist;
2269 return playlist->amount;
2272 /* returns full path of playlist (minus extension) */
2273 char *playlist_name(const struct playlist_info* playlist, char *buf,
2274 int buf_size)
2276 char *sep;
2278 if (!playlist)
2279 playlist = &current_playlist;
2281 snprintf(buf, buf_size, "%s", playlist->filename+playlist->dirlen);
2283 if (!buf[0])
2284 return NULL;
2286 /* Remove extension */
2287 sep = strrchr(buf, '.');
2288 if (sep)
2289 *sep = 0;
2291 return buf;
2294 /* returns the playlist filename */
2295 char *playlist_get_name(const struct playlist_info* playlist, char *buf,
2296 int buf_size)
2298 if (!playlist)
2299 playlist = &current_playlist;
2301 snprintf(buf, buf_size, "%s", playlist->filename);
2303 if (!buf[0])
2304 return NULL;
2306 return buf;
2309 /* Fills info structure with information about track at specified index.
2310 Returns 0 on success and -1 on failure */
2311 int playlist_get_track_info(struct playlist_info* playlist, int index,
2312 struct playlist_track_info* info)
2314 int seek;
2315 bool control_file;
2317 if (!playlist)
2318 playlist = &current_playlist;
2320 if (index < 0 || index >= playlist->amount)
2321 return -1;
2323 control_file = playlist->indices[index] & PLAYLIST_INSERT_TYPE_MASK;
2324 seek = playlist->indices[index] & PLAYLIST_SEEK_MASK;
2326 if (get_filename(playlist, seek, control_file, info->filename,
2327 sizeof(info->filename)) < 0)
2328 return -1;
2330 info->attr = 0;
2332 if (control_file)
2334 if (playlist->indices[index] & PLAYLIST_QUEUE_MASK)
2335 info->attr |= PLAYLIST_ATTR_QUEUED;
2336 else
2337 info->attr |= PLAYLIST_ATTR_INSERTED;
2340 info->index = index;
2341 info->display_index = rotate_index(playlist, index) + 1;
2343 return 0;
2346 /* save the current dynamic playlist to specified file */
2347 int playlist_save(struct playlist_info* playlist, char *filename)
2349 int fd;
2350 int i, index;
2351 int count = 0;
2352 char tmp_buf[MAX_PATH+1];
2353 int result = 0;
2355 if (!playlist)
2356 playlist = &current_playlist;
2358 if (playlist->amount <= 0)
2359 return -1;
2361 /* use current working directory as base for pathname */
2362 if (format_track_path(tmp_buf, filename, sizeof(tmp_buf),
2363 strlen(filename)+1, getcwd(NULL, -1)) < 0)
2364 return -1;
2366 fd = open(tmp_buf, O_CREAT|O_WRONLY|O_TRUNC);
2367 if (fd < 0)
2369 splash(HZ*2, true, str(LANG_PLAYLIST_ACCESS_ERROR));
2370 return -1;
2373 display_playlist_count(count, str(LANG_PLAYLIST_SAVE_COUNT));
2375 index = playlist->first_index;
2376 for (i=0; i<playlist->amount; i++)
2378 bool control_file;
2379 bool queue;
2380 int seek;
2382 /* user abort */
2383 if (button_get(false) == SETTINGS_CANCEL)
2384 break;
2386 control_file = playlist->indices[index] & PLAYLIST_INSERT_TYPE_MASK;
2387 queue = playlist->indices[index] & PLAYLIST_QUEUE_MASK;
2388 seek = playlist->indices[index] & PLAYLIST_SEEK_MASK;
2390 /* Don't save queued files */
2391 if (!queue)
2393 if (get_filename(playlist, seek, control_file, tmp_buf,
2394 MAX_PATH+1) < 0)
2396 result = -1;
2397 break;
2400 if (fprintf(fd, "%s\n", tmp_buf) < 0)
2402 splash(HZ*2, true, str(LANG_PLAYLIST_CONTROL_UPDATE_ERROR));
2403 result = -1;
2404 break;
2407 count++;
2409 if ((count % PLAYLIST_DISPLAY_COUNT) == 0)
2410 display_playlist_count(count, str(LANG_PLAYLIST_SAVE_COUNT));
2412 yield();
2415 index = (index+1)%playlist->amount;
2418 display_playlist_count(count, str(LANG_PLAYLIST_SAVE_COUNT));
2420 close(fd);
2422 return result;