Now fully v1 spec compliant
[kugel-rb.git] / apps / playlist.c
blobe514530aea9638973749cd52b89b6d40e68d61b2
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 "tree.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 bool buffer_full = false;
541 int i;
542 int dirfilter = SHOW_ALL;
543 struct entry *files;
545 /* use the tree browser dircache to load files */
546 files = load_and_sort_directory(dirname, &dirfilter, &num_files,
547 &buffer_full);
549 if(!files)
551 splash(HZ*2, true, str(LANG_PLAYLIST_DIRECTORY_ACCESS_ERROR));
552 return 0;
555 /* we've overwritten the dircache so tree browser will need to be
556 reloaded */
557 reload_directory();
559 if (queue)
560 count_str = str(LANG_PLAYLIST_QUEUE_COUNT);
561 else
562 count_str = str(LANG_PLAYLIST_INSERT_COUNT);
564 for (i=0; i<num_files; i++)
566 /* user abort */
567 if (button_get(false) == SETTINGS_CANCEL)
569 result = -1;
570 break;
573 if (files[i].attr & ATTR_DIRECTORY)
575 if (recurse)
577 /* recursively add directories */
578 snprintf(buf, sizeof(buf), "%s/%s", dirname, files[i].name);
579 result = add_directory_to_playlist(playlist, buf, position,
580 queue, count, recurse);
581 if (result < 0)
582 break;
584 /* we now need to reload our current directory */
585 files = load_and_sort_directory(dirname, &dirfilter, &num_files,
586 &buffer_full);
587 if (!files)
589 result = -1;
590 break;
593 else
594 continue;
596 else if ((files[i].attr & TREE_ATTR_MASK) == TREE_ATTR_MPA)
598 int insert_pos;
600 snprintf(buf, sizeof(buf), "%s/%s", dirname, files[i].name);
602 insert_pos = add_track_to_playlist(playlist, buf, *position,
603 queue, -1);
604 if (insert_pos < 0)
606 result = -1;
607 break;
610 (*count)++;
612 /* Make sure tracks are inserted in correct order if user requests
613 INSERT_FIRST */
614 if (*position == PLAYLIST_INSERT_FIRST || *position >= 0)
615 *position = insert_pos + 1;
617 if ((*count%PLAYLIST_DISPLAY_COUNT) == 0)
619 display_playlist_count(*count, count_str);
621 if (*count == PLAYLIST_DISPLAY_COUNT)
622 mpeg_flush_and_reload_tracks();
625 /* let the other threads work */
626 yield();
630 return result;
634 * remove track at specified position
636 static int remove_track_from_playlist(struct playlist_info* playlist,
637 int position, bool write)
639 int i;
640 bool inserted;
642 if (playlist->amount <= 0)
643 return -1;
645 inserted = playlist->indices[position] & PLAYLIST_INSERT_TYPE_MASK;
647 /* shift indices now that track has been removed */
648 for (i=position; i<playlist->amount; i++)
649 playlist->indices[i] = playlist->indices[i+1];
651 playlist->amount--;
653 if (inserted)
654 playlist->num_inserted_tracks--;
655 else
656 playlist->deleted = true;
658 /* update stored indices if needed */
659 if (position < playlist->index)
660 playlist->index--;
662 if (position < playlist->first_index)
664 playlist->first_index--;
666 if (write)
668 global_settings.resume_first_index = playlist->first_index;
669 settings_save();
673 if (position <= playlist->last_insert_pos)
674 playlist->last_insert_pos--;
676 if (write && playlist->control_fd >= 0)
678 int result = -1;
680 if (flush_pending_control(playlist) < 0)
681 return -1;
683 mutex_lock(&playlist->control_mutex);
685 if (lseek(playlist->control_fd, 0, SEEK_END) >= 0)
687 if (fprintf(playlist->control_fd, "D:%d\n", position) > 0)
689 fsync(playlist->control_fd);
690 result = 0;
694 mutex_unlock(&playlist->control_mutex);
696 if (result < 0)
698 splash(HZ*2, true, str(LANG_PLAYLIST_CONTROL_UPDATE_ERROR));
699 return result;
703 return 0;
707 * randomly rearrange the array of indices for the playlist. If start_current
708 * is true then update the index to the new index of the current playing track
710 static int randomise_playlist(struct playlist_info* playlist,
711 unsigned int seed, bool start_current,
712 bool write)
714 int count;
715 int candidate;
716 int store;
717 unsigned int current = playlist->indices[playlist->index];
719 /* seed 0 is used to identify sorted playlist for resume purposes */
720 if (seed == 0)
721 seed = 1;
723 /* seed with the given seed */
724 srand(seed);
726 /* randomise entire indices list */
727 for(count = playlist->amount - 1; count >= 0; count--)
729 /* the rand is from 0 to RAND_MAX, so adjust to our value range */
730 candidate = rand() / (RAND_MAX / (count + 1));
732 /* now swap the values at the 'count' and 'candidate' positions */
733 store = playlist->indices[candidate];
734 playlist->indices[candidate] = playlist->indices[count];
735 playlist->indices[count] = store;
738 if (start_current)
739 find_and_set_playlist_index(playlist, current);
741 /* indices have been moved so last insert position is no longer valid */
742 playlist->last_insert_pos = -1;
744 playlist->seed = seed;
745 if (playlist->num_inserted_tracks > 0 || playlist->deleted)
746 playlist->shuffle_modified = true;
748 if (write)
750 /* Don't write to disk immediately. Instead, save in settings and
751 only flush if playlist is modified (insertion/deletion) */
752 playlist->shuffle_flush = true;
753 global_settings.resume_seed = seed;
754 settings_save();
757 return 0;
761 * Sort the array of indices for the playlist. If start_current is true then
762 * set the index to the new index of the current song.
764 static int sort_playlist(struct playlist_info* playlist, bool start_current,
765 bool write)
767 unsigned int current = playlist->indices[playlist->index];
769 if (playlist->amount > 0)
770 qsort(playlist->indices, playlist->amount, sizeof(playlist->indices[0]),
771 compare);
773 if (start_current)
774 find_and_set_playlist_index(playlist, current);
776 /* indices have been moved so last insert position is no longer valid */
777 playlist->last_insert_pos = -1;
779 if (!playlist->num_inserted_tracks && !playlist->deleted)
780 playlist->shuffle_modified = false;
781 if (write && playlist->control_fd >= 0)
783 /* Don't write to disk immediately. Instead, save in settings and
784 only flush if playlist is modified (insertion/deletion) */
785 playlist->shuffle_flush = true;
786 global_settings.resume_seed = 0;
787 settings_save();
790 return 0;
794 * returns the index of the track that is "steps" away from current playing
795 * track.
797 static int get_next_index(const struct playlist_info* playlist, int steps)
799 int current_index = playlist->index;
800 int next_index = -1;
802 if (playlist->amount <= 0)
803 return -1;
805 switch (global_settings.repeat_mode)
807 case REPEAT_OFF:
809 current_index = rotate_index(playlist, current_index);
811 next_index = current_index+steps;
812 if ((next_index < 0) || (next_index >= playlist->amount))
813 next_index = -1;
814 else
815 next_index = (next_index+playlist->first_index) %
816 playlist->amount;
818 break;
821 case REPEAT_ONE:
822 next_index = current_index;
823 break;
825 case REPEAT_ALL:
826 default:
828 next_index = (current_index+steps) % playlist->amount;
829 while (next_index < 0)
830 next_index += playlist->amount;
832 if (steps >= playlist->amount)
834 int i, index;
836 index = next_index;
837 next_index = -1;
839 /* second time around so skip the queued files */
840 for (i=0; i<playlist->amount; i++)
842 if (playlist->indices[index] & PLAYLIST_QUEUE_MASK)
843 index = (index+1) % playlist->amount;
844 else
846 next_index = index;
847 break;
851 break;
855 return next_index;
859 * Search for the seek track and set appropriate indices. Used after shuffle
860 * to make sure the current index is still pointing to correct track.
862 static void find_and_set_playlist_index(struct playlist_info* playlist,
863 unsigned int seek)
865 int i;
867 /* Set the index to the current song */
868 for (i=0; i<playlist->amount; i++)
870 if (playlist->indices[i] == seek)
872 playlist->index = playlist->first_index = i;
874 if (playlist->current)
876 global_settings.resume_first_index = i;
877 settings_save();
880 break;
886 * used to sort track indices. Sort order is as follows:
887 * 1. Prepended tracks (in prepend order)
888 * 2. Playlist/directory tracks (in playlist order)
889 * 3. Inserted/Appended tracks (in insert order)
891 static int compare(const void* p1, const void* p2)
893 unsigned int* e1 = (unsigned int*) p1;
894 unsigned int* e2 = (unsigned int*) p2;
895 unsigned int flags1 = *e1 & PLAYLIST_INSERT_TYPE_MASK;
896 unsigned int flags2 = *e2 & PLAYLIST_INSERT_TYPE_MASK;
898 if (flags1 == flags2)
899 return (*e1 & PLAYLIST_SEEK_MASK) - (*e2 & PLAYLIST_SEEK_MASK);
900 else if (flags1 == PLAYLIST_INSERT_TYPE_PREPEND ||
901 flags2 == PLAYLIST_INSERT_TYPE_APPEND)
902 return -1;
903 else if (flags1 == PLAYLIST_INSERT_TYPE_APPEND ||
904 flags2 == PLAYLIST_INSERT_TYPE_PREPEND)
905 return 1;
906 else if (flags1 && flags2)
907 return (*e1 & PLAYLIST_SEEK_MASK) - (*e2 & PLAYLIST_SEEK_MASK);
908 else
909 return *e1 - *e2;
913 * gets pathname for track at seek index
915 static int get_filename(struct playlist_info* playlist, int seek,
916 bool control_file, char *buf, int buf_length)
918 int fd;
919 int max = -1;
920 char tmp_buf[MAX_PATH+1];
921 char dir_buf[MAX_PATH+1];
923 if (buf_length > MAX_PATH+1)
924 buf_length = MAX_PATH+1;
926 if (playlist->in_ram && !control_file)
928 strncpy(tmp_buf, &playlist->buffer[seek], sizeof(tmp_buf));
929 tmp_buf[MAX_PATH] = '\0';
930 max = strlen(tmp_buf) + 1;
932 else
934 if (control_file)
935 fd = playlist->control_fd;
936 else
938 if(-1 == playlist->fd)
939 playlist->fd = open(playlist->filename, O_RDONLY);
941 fd = playlist->fd;
944 if(-1 != fd)
946 if (control_file)
947 mutex_lock(&playlist->control_mutex);
949 lseek(fd, seek, SEEK_SET);
950 max = read(fd, tmp_buf, buf_length);
952 if (control_file)
953 mutex_unlock(&playlist->control_mutex);
956 if (max < 0)
958 if (control_file)
959 splash(HZ*2, true,
960 str(LANG_PLAYLIST_CONTROL_ACCESS_ERROR));
961 else
962 splash(HZ*2, true, str(LANG_PLAYLIST_ACCESS_ERROR));
964 return max;
968 strncpy(dir_buf, playlist->filename, playlist->dirlen-1);
969 dir_buf[playlist->dirlen-1] = 0;
971 return (format_track_path(buf, tmp_buf, buf_length, max, dir_buf));
975 * Returns absolute path of track
977 static int format_track_path(char *dest, char *src, int buf_length, int max,
978 char *dir)
980 int i = 0;
981 int j;
982 char *temp_ptr;
984 /* Zero-terminate the file name */
985 while((src[i] != '\n') &&
986 (src[i] != '\r') &&
987 (i < max))
988 i++;
990 /* Now work back killing white space */
991 while((src[i-1] == ' ') ||
992 (src[i-1] == '\t'))
993 i--;
995 src[i]=0;
997 /* replace backslashes with forward slashes */
998 for ( j=0; j<i; j++ )
999 if ( src[j] == '\\' )
1000 src[j] = '/';
1002 if('/' == src[0])
1004 strncpy(dest, src, buf_length);
1006 else
1008 /* handle dos style drive letter */
1009 if (':' == src[1])
1010 strncpy(dest, &src[2], buf_length);
1011 else if (!strncmp(src, "../", 3))
1013 /* handle relative paths */
1014 i=3;
1015 while(!strncmp(&src[i], "../", 3))
1016 i += 3;
1017 for (j=0; j<i/3; j++) {
1018 temp_ptr = strrchr(dir, '/');
1019 if (temp_ptr)
1020 *temp_ptr = '\0';
1021 else
1022 break;
1024 snprintf(dest, buf_length, "%s/%s", dir, &src[i]);
1026 else if ( '.' == src[0] && '/' == src[1] ) {
1027 snprintf(dest, buf_length, "%s/%s", dir, &src[2]);
1029 else {
1030 snprintf(dest, buf_length, "%s/%s", dir, src);
1034 return 0;
1038 * Display splash message showing progress of playlist/directory insertion or
1039 * save.
1041 static void display_playlist_count(int count, const char *fmt)
1043 lcd_clear_display();
1045 #ifdef HAVE_LCD_BITMAP
1046 if(global_settings.statusbar)
1047 lcd_setmargins(0, STATUSBAR_HEIGHT);
1048 else
1049 lcd_setmargins(0, 0);
1050 #endif
1052 splash(0, true, fmt, count,
1053 #if CONFIG_KEYPAD == PLAYER_PAD
1054 str(LANG_STOP_ABORT)
1055 #else
1056 str(LANG_OFF_ABORT)
1057 #endif
1062 * Display buffer full message
1064 static void display_buffer_full(void)
1066 splash(HZ*2, true, "%s %s",
1067 str(LANG_PLAYINDICES_PLAYLIST),
1068 str(LANG_PLAYINDICES_BUFFER));
1072 * Flush any pending control commands to disk. Called when playlist is being
1073 * modified. Returns 0 on success and -1 on failure.
1075 static int flush_pending_control(struct playlist_info* playlist)
1077 int result = 0;
1079 if (playlist->shuffle_flush && global_settings.resume_seed >= 0)
1081 /* pending shuffle */
1082 mutex_lock(&playlist->control_mutex);
1084 if (lseek(playlist->control_fd, 0, SEEK_END) >= 0)
1086 if (global_settings.resume_seed == 0)
1087 result = fprintf(playlist->control_fd, "U:%d\n",
1088 playlist->first_index);
1089 else
1090 result = fprintf(playlist->control_fd, "S:%d:%d\n",
1091 global_settings.resume_seed, playlist->first_index);
1093 if (result > 0)
1095 fsync(playlist->control_fd);
1097 playlist->shuffle_flush = false;
1098 global_settings.resume_seed = -1;
1099 settings_save();
1101 result = 0;
1103 else
1104 result = -1;
1106 else
1107 result = -1;
1109 mutex_unlock(&playlist->control_mutex);
1111 if (result < 0)
1113 splash(HZ*2, true, str(LANG_PLAYLIST_CONTROL_UPDATE_ERROR));
1114 return result;
1118 return result;
1122 * Rotate indices such that first_index is index 0
1124 static int rotate_index(const struct playlist_info* playlist, int index)
1126 index -= playlist->first_index;
1127 if (index < 0)
1128 index += playlist->amount;
1130 return index;
1134 * Initialize playlist entries at startup
1136 void playlist_init(void)
1138 struct playlist_info* playlist = &current_playlist;
1140 playlist->current = true;
1141 snprintf(playlist->control_filename, sizeof(playlist->control_filename),
1142 "%s", PLAYLIST_CONTROL_FILE);
1143 playlist->fd = -1;
1144 playlist->control_fd = -1;
1145 playlist->max_playlist_size = global_settings.max_files_in_playlist;
1146 playlist->indices = buffer_alloc(playlist->max_playlist_size * sizeof(int));
1147 playlist->buffer_size =
1148 AVERAGE_FILENAME_LENGTH * global_settings.max_files_in_dir;
1149 playlist->buffer = buffer_alloc(playlist->buffer_size);
1150 mutex_init(&playlist->control_mutex);
1151 empty_playlist(playlist, true);
1155 * Create new playlist
1157 int playlist_create(const char *dir, const char *file)
1159 struct playlist_info* playlist = &current_playlist;
1161 new_playlist(playlist, dir, file);
1163 if (file)
1164 /* load the playlist file */
1165 add_indices_to_playlist(playlist, NULL, 0);
1167 return 0;
1170 #define PLAYLIST_COMMAND_SIZE (MAX_PATH+12)
1173 * Restore the playlist state based on control file commands. Called to
1174 * resume playback after shutdown.
1176 int playlist_resume(void)
1178 struct playlist_info* playlist = &current_playlist;
1179 char *buffer;
1180 int buflen;
1181 int nread;
1182 int total_read = 0;
1183 int control_file_size = 0;
1184 bool first = true;
1185 bool sorted = true;
1187 enum {
1188 resume_playlist,
1189 resume_add,
1190 resume_queue,
1191 resume_delete,
1192 resume_shuffle,
1193 resume_unshuffle,
1194 resume_reset,
1195 resume_comment
1198 /* use mp3 buffer for maximum load speed */
1199 talk_buffer_steal(); /* we use the mp3 buffer, need to tell */
1200 buflen = (mp3end - mp3buf);
1201 buffer = mp3buf;
1203 empty_playlist(playlist, true);
1205 playlist->control_fd = open(playlist->control_filename, O_RDWR);
1206 if (playlist->control_fd < 0)
1208 splash(HZ*2, true, str(LANG_PLAYLIST_CONTROL_ACCESS_ERROR));
1209 return -1;
1211 playlist->control_created = true;
1213 control_file_size = filesize(playlist->control_fd);
1214 if (control_file_size <= 0)
1216 splash(HZ*2, true, str(LANG_PLAYLIST_CONTROL_ACCESS_ERROR));
1217 return -1;
1220 /* read a small amount first to get the header */
1221 nread = read(playlist->control_fd, buffer,
1222 PLAYLIST_COMMAND_SIZE<buflen?PLAYLIST_COMMAND_SIZE:buflen);
1223 if(nread <= 0)
1225 splash(HZ*2, true, str(LANG_PLAYLIST_CONTROL_ACCESS_ERROR));
1226 return -1;
1229 while (1)
1231 int result = 0;
1232 int count;
1233 int current_command = resume_comment;
1234 int last_newline = 0;
1235 int str_count = -1;
1236 bool newline = true;
1237 bool exit_loop = false;
1238 char *p = buffer;
1239 char *str1 = NULL;
1240 char *str2 = NULL;
1241 char *str3 = NULL;
1243 for(count=0; count<nread && !exit_loop; count++,p++)
1245 /* Are we on a new line? */
1246 if((*p == '\n') || (*p == '\r'))
1248 *p = '\0';
1250 /* save last_newline in case we need to load more data */
1251 last_newline = count;
1253 switch (current_command)
1255 case resume_playlist:
1257 /* str1=version str2=dir str3=file */
1258 int version;
1260 if (!str1)
1262 result = -1;
1263 exit_loop = true;
1264 break;
1267 if (!str2)
1268 str2 = "";
1270 if (!str3)
1271 str3 = "";
1273 version = atoi(str1);
1275 if (version != PLAYLIST_CONTROL_FILE_VERSION)
1276 return -1;
1278 update_playlist_filename(playlist, str2, str3);
1280 if (str3[0] != '\0')
1282 /* NOTE: add_indices_to_playlist() overwrites the
1283 mp3buf so we need to reload control file
1284 data */
1285 add_indices_to_playlist(playlist, NULL, 0);
1287 else if (str2[0] != '\0')
1289 playlist->in_ram = true;
1290 resume_directory(str2);
1293 /* load the rest of the data */
1294 first = false;
1295 exit_loop = true;
1297 break;
1299 case resume_add:
1300 case resume_queue:
1302 /* str1=position str2=last_position str3=file */
1303 int position, last_position;
1304 bool queue;
1306 if (!str1 || !str2 || !str3)
1308 result = -1;
1309 exit_loop = true;
1310 break;
1313 position = atoi(str1);
1314 last_position = atoi(str2);
1316 queue = (current_command == resume_add)?false:true;
1318 /* seek position is based on str3's position in
1319 buffer */
1320 if (add_track_to_playlist(playlist, str3, position,
1321 queue, total_read+(str3-buffer)) < 0)
1322 return -1;
1324 playlist->last_insert_pos = last_position;
1326 break;
1328 case resume_delete:
1330 /* str1=position */
1331 int position;
1333 if (!str1)
1335 result = -1;
1336 exit_loop = true;
1337 break;
1340 position = atoi(str1);
1342 if (remove_track_from_playlist(playlist, position,
1343 false) < 0)
1344 return -1;
1346 break;
1348 case resume_shuffle:
1350 /* str1=seed str2=first_index */
1351 int seed;
1353 if (!str1 || !str2)
1355 result = -1;
1356 exit_loop = true;
1357 break;
1360 if (!sorted)
1362 /* Always sort list before shuffling */
1363 sort_playlist(playlist, false, false);
1366 seed = atoi(str1);
1367 playlist->first_index = atoi(str2);
1369 if (randomise_playlist(playlist, seed, false,
1370 false) < 0)
1371 return -1;
1373 sorted = false;
1374 break;
1376 case resume_unshuffle:
1378 /* str1=first_index */
1379 if (!str1)
1381 result = -1;
1382 exit_loop = true;
1383 break;
1386 playlist->first_index = atoi(str1);
1388 if (sort_playlist(playlist, false, false) < 0)
1389 return -1;
1391 sorted = true;
1392 break;
1394 case resume_reset:
1396 playlist->last_insert_pos = -1;
1397 break;
1399 case resume_comment:
1400 default:
1401 break;
1404 newline = true;
1406 /* to ignore any extra newlines */
1407 current_command = resume_comment;
1409 else if(newline)
1411 newline = false;
1413 /* first non-comment line must always specify playlist */
1414 if (first && *p != 'P' && *p != '#')
1416 result = -1;
1417 exit_loop = true;
1418 break;
1421 switch (*p)
1423 case 'P':
1424 /* playlist can only be specified once */
1425 if (!first)
1427 result = -1;
1428 exit_loop = true;
1429 break;
1432 current_command = resume_playlist;
1433 break;
1434 case 'A':
1435 current_command = resume_add;
1436 break;
1437 case 'Q':
1438 current_command = resume_queue;
1439 break;
1440 case 'D':
1441 current_command = resume_delete;
1442 break;
1443 case 'S':
1444 current_command = resume_shuffle;
1445 break;
1446 case 'U':
1447 current_command = resume_unshuffle;
1448 break;
1449 case 'R':
1450 current_command = resume_reset;
1451 break;
1452 case '#':
1453 current_command = resume_comment;
1454 break;
1455 default:
1456 result = -1;
1457 exit_loop = true;
1458 break;
1461 str_count = -1;
1462 str1 = NULL;
1463 str2 = NULL;
1464 str3 = NULL;
1466 else if(current_command != resume_comment)
1468 /* all control file strings are separated with a colon.
1469 Replace the colon with 0 to get proper strings that can be
1470 used by commands above */
1471 if (*p == ':')
1473 *p = '\0';
1474 str_count++;
1476 if ((count+1) < nread)
1478 switch (str_count)
1480 case 0:
1481 str1 = p+1;
1482 break;
1483 case 1:
1484 str2 = p+1;
1485 break;
1486 case 2:
1487 str3 = p+1;
1488 break;
1489 default:
1490 /* allow last string to contain colons */
1491 *p = ':';
1492 break;
1499 if (result < 0)
1501 splash(HZ*2, true, str(LANG_PLAYLIST_CONTROL_INVALID));
1502 return result;
1505 if (!newline || (exit_loop && count<nread))
1507 if ((total_read + count) >= control_file_size)
1509 /* no newline at end of control file */
1510 splash(HZ*2, true, str(LANG_PLAYLIST_CONTROL_INVALID));
1511 return -1;
1514 /* We didn't end on a newline or we exited loop prematurely.
1515 Either way, re-read the remainder. */
1516 count = last_newline;
1517 lseek(playlist->control_fd, total_read+count, SEEK_SET);
1520 total_read += count;
1522 if (first)
1523 /* still looking for header */
1524 nread = read(playlist->control_fd, buffer,
1525 PLAYLIST_COMMAND_SIZE<buflen?PLAYLIST_COMMAND_SIZE:buflen);
1526 else
1527 nread = read(playlist->control_fd, buffer, buflen);
1529 /* Terminate on EOF */
1530 if(nread <= 0)
1532 if (global_settings.resume_seed >= 0)
1534 /* Apply shuffle command saved in settings */
1535 if (global_settings.resume_seed == 0)
1536 sort_playlist(playlist, false, true);
1537 else
1539 if (!sorted)
1540 sort_playlist(playlist, false, false);
1542 randomise_playlist(playlist, global_settings.resume_seed,
1543 false, true);
1546 playlist->first_index = global_settings.resume_first_index;
1549 break;
1553 return 0;
1557 * Add track to in_ram playlist. Used when playing directories.
1559 int playlist_add(const char *filename)
1561 struct playlist_info* playlist = &current_playlist;
1562 int len = strlen(filename);
1564 if((len+1 > playlist->buffer_size - playlist->buffer_end_pos) ||
1565 (playlist->amount >= playlist->max_playlist_size))
1567 display_buffer_full();
1568 return -1;
1571 playlist->indices[playlist->amount++] = playlist->buffer_end_pos;
1573 strcpy(&playlist->buffer[playlist->buffer_end_pos], filename);
1574 playlist->buffer_end_pos += len;
1575 playlist->buffer[playlist->buffer_end_pos++] = '\0';
1577 return 0;
1580 /* shuffle newly created playlist using random seed. */
1581 int playlist_shuffle(int random_seed, int start_index)
1583 struct playlist_info* playlist = &current_playlist;
1585 unsigned int seek_pos = 0;
1586 bool start_current = false;
1588 if (start_index >= 0 && global_settings.play_selected)
1590 /* store the seek position before the shuffle */
1591 seek_pos = playlist->indices[start_index];
1592 playlist->index = global_settings.resume_first_index =
1593 playlist->first_index = start_index;
1594 start_current = true;
1597 splash(0, true, str(LANG_PLAYLIST_SHUFFLE));
1599 randomise_playlist(playlist, random_seed, start_current, true);
1601 /* Flush shuffle command to disk */
1602 flush_pending_control(playlist);
1604 return playlist->index;
1607 /* start playing current playlist at specified index/offset */
1608 int playlist_start(int start_index, int offset)
1610 struct playlist_info* playlist = &current_playlist;
1612 playlist->index = start_index;
1613 talk_buffer_steal(); /* will use the mp3 buffer */
1614 mpeg_play(offset);
1616 return 0;
1619 /* Returns false if 'steps' is out of bounds, else true */
1620 bool playlist_check(int steps)
1622 struct playlist_info* playlist = &current_playlist;
1623 int index = get_next_index(playlist, steps);
1624 return (index >= 0);
1627 /* get trackname of track that is "steps" away from current playing track.
1628 NULL is used to identify end of playlist */
1629 char* playlist_peek(int steps)
1631 struct playlist_info* playlist = &current_playlist;
1632 int seek;
1633 int fd;
1634 char *temp_ptr;
1635 int index;
1636 bool control_file;
1638 index = get_next_index(playlist, steps);
1639 if (index < 0)
1640 return NULL;
1642 control_file = playlist->indices[index] & PLAYLIST_INSERT_TYPE_MASK;
1643 seek = playlist->indices[index] & PLAYLIST_SEEK_MASK;
1645 if (get_filename(playlist, seek, control_file, now_playing,
1646 MAX_PATH+1) < 0)
1647 return NULL;
1649 temp_ptr = now_playing;
1651 if (!playlist->in_ram || control_file)
1653 /* remove bogus dirs from beginning of path
1654 (workaround for buggy playlist creation tools) */
1655 while (temp_ptr)
1657 fd = open(temp_ptr, O_RDONLY);
1658 if (fd >= 0)
1660 close(fd);
1661 break;
1664 temp_ptr = strchr(temp_ptr+1, '/');
1667 if (!temp_ptr)
1669 /* Even though this is an invalid file, we still need to pass a
1670 file name to the caller because NULL is used to indicate end
1671 of playlist */
1672 return now_playing;
1676 return temp_ptr;
1680 * Update indices as track has changed
1682 int playlist_next(int steps)
1684 struct playlist_info* playlist = &current_playlist;
1685 int index;
1687 if (steps > 0 && global_settings.repeat_mode != REPEAT_ONE)
1689 int i, j;
1691 /* We need to delete all the queued songs */
1692 for (i=0, j=steps; i<j; i++)
1694 index = get_next_index(playlist, i);
1696 if (playlist->indices[index] & PLAYLIST_QUEUE_MASK)
1698 remove_track_from_playlist(playlist, index, true);
1699 steps--; /* one less track */
1704 index = get_next_index(playlist, steps);
1705 playlist->index = index;
1707 if (playlist->last_insert_pos >= 0 && steps > 0)
1709 /* check to see if we've gone beyond the last inserted track */
1710 int cur = rotate_index(playlist, index);
1711 int last_pos = rotate_index(playlist, playlist->last_insert_pos);
1713 if (cur > last_pos)
1715 /* reset last inserted track */
1716 playlist->last_insert_pos = -1;
1718 if (playlist->control_fd >= 0)
1720 int result = -1;
1722 mutex_lock(&playlist->control_mutex);
1724 if (lseek(playlist->control_fd, 0, SEEK_END) >= 0)
1726 if (fprintf(playlist->control_fd, "R\n") > 0)
1728 fsync(playlist->control_fd);
1729 result = 0;
1733 mutex_unlock(&playlist->control_mutex);
1735 if (result < 0)
1737 splash(HZ*2, true,
1738 str(LANG_PLAYLIST_CONTROL_UPDATE_ERROR));
1739 return result;
1745 return index;
1748 /* Get resume info for current playing song. If return value is -1 then
1749 settings shouldn't be saved. */
1750 int playlist_get_resume_info(int *resume_index)
1752 struct playlist_info* playlist = &current_playlist;
1754 *resume_index = playlist->index;
1756 return 0;
1759 /* Returns index of current playing track for display purposes. This value
1760 should not be used for resume purposes as it doesn't represent the actual
1761 index into the playlist */
1762 int playlist_get_display_index(void)
1764 struct playlist_info* playlist = &current_playlist;
1766 /* first_index should always be index 0 for display purposes */
1767 int index = rotate_index(playlist, playlist->index);
1769 return (index+1);
1772 /* returns number of tracks in current playlist */
1773 int playlist_amount(void)
1775 return playlist_amount_ex(NULL);
1779 * Create a new playlist If playlist is not NULL then we're loading a
1780 * playlist off disk for viewing/editing. The index_buffer is used to store
1781 * playlist indices (required for and only used if !current playlist). The
1782 * temp_buffer (if not NULL) is used as a scratchpad when loading indices.
1784 int playlist_create_ex(struct playlist_info* playlist,
1785 const char* dir, const char* file,
1786 void* index_buffer, int index_buffer_size,
1787 void* temp_buffer, int temp_buffer_size)
1789 if (!playlist)
1790 playlist = &current_playlist;
1791 else
1793 /* Initialize playlist structure */
1794 int r = rand() % 10;
1795 playlist->current = false;
1797 /* Use random name for control file */
1798 snprintf(playlist->control_filename, sizeof(playlist->control_filename),
1799 "%s.%d", PLAYLIST_CONTROL_FILE, r);
1800 playlist->fd = -1;
1801 playlist->control_fd = -1;
1803 if (index_buffer)
1805 int num_indices = index_buffer_size / sizeof(int);
1807 if (num_indices > global_settings.max_files_in_playlist)
1808 num_indices = global_settings.max_files_in_playlist;
1810 playlist->max_playlist_size = num_indices;
1811 playlist->indices = index_buffer;
1813 else
1815 playlist->max_playlist_size = current_playlist.max_playlist_size;
1816 playlist->indices = current_playlist.indices;
1819 playlist->buffer_size = 0;
1820 playlist->buffer = NULL;
1821 mutex_init(&playlist->control_mutex);
1824 new_playlist(playlist, dir, file);
1826 if (file)
1827 /* load the playlist file */
1828 add_indices_to_playlist(playlist, temp_buffer, temp_buffer_size);
1830 return 0;
1834 * Set the specified playlist as the current.
1835 * NOTE: You will get undefined behaviour if something is already playing so
1836 * remember to stop before calling this. Also, this call will
1837 * effectively close your playlist, making it unusable.
1839 int playlist_set_current(struct playlist_info* playlist)
1841 if (!playlist || (check_control(playlist) < 0))
1842 return -1;
1844 empty_playlist(&current_playlist, false);
1846 strncpy(current_playlist.filename, playlist->filename,
1847 sizeof(current_playlist.filename));
1849 current_playlist.fd = playlist->fd;
1851 close(playlist->control_fd);
1852 remove(current_playlist.control_filename);
1853 if (rename(playlist->control_filename,
1854 current_playlist.control_filename) < 0)
1855 return -1;
1856 current_playlist.control_fd = open(current_playlist.control_filename,
1857 O_RDWR);
1858 if (current_playlist.control_fd < 0)
1859 return -1;
1860 current_playlist.control_created = true;
1862 current_playlist.dirlen = playlist->dirlen;
1864 if (playlist->indices && playlist->indices != current_playlist.indices)
1865 memcpy(current_playlist.indices, playlist->indices,
1866 playlist->max_playlist_size*sizeof(int));
1868 current_playlist.first_index = playlist->first_index;
1869 current_playlist.amount = playlist->amount;
1870 current_playlist.last_insert_pos = playlist->last_insert_pos;
1871 current_playlist.seed = playlist->seed;
1872 current_playlist.shuffle_modified = playlist->shuffle_modified;
1873 current_playlist.deleted = playlist->deleted;
1874 current_playlist.num_inserted_tracks = playlist->num_inserted_tracks;
1875 current_playlist.shuffle_flush = playlist->shuffle_flush;
1877 return 0;
1881 * Close files and delete control file for non-current playlist.
1883 void playlist_close(struct playlist_info* playlist)
1885 if (!playlist)
1886 return;
1888 if (playlist->fd >= 0)
1889 close(playlist->fd);
1891 if (playlist->control_fd >= 0)
1892 close(playlist->control_fd);
1894 if (playlist->control_created)
1895 remove(playlist->control_filename);
1899 * Insert track into playlist at specified position (or one of the special
1900 * positions). Returns position where track was inserted or -1 if error.
1902 int playlist_insert_track(struct playlist_info* playlist, const char *filename,
1903 int position, bool queue)
1905 int result;
1907 if (!playlist)
1908 playlist = &current_playlist;
1910 if (check_control(playlist) < 0)
1912 splash(HZ*2, true, str(LANG_PLAYLIST_CONTROL_ACCESS_ERROR));
1913 return -1;
1916 result = add_track_to_playlist(playlist, filename, position, queue, -1);
1918 if (result != -1)
1920 fsync(playlist->control_fd);
1921 mpeg_flush_and_reload_tracks();
1924 return result;
1928 * Insert all tracks from specified directory into playlist.
1930 int playlist_insert_directory(struct playlist_info* playlist,
1931 const char *dirname, int position, bool queue,
1932 bool recurse)
1934 int count = 0;
1935 int result;
1936 char *count_str;
1938 if (!playlist)
1939 playlist = &current_playlist;
1941 if (check_control(playlist) < 0)
1943 splash(HZ*2, true, str(LANG_PLAYLIST_CONTROL_ACCESS_ERROR));
1944 return -1;
1947 if (queue)
1948 count_str = str(LANG_PLAYLIST_QUEUE_COUNT);
1949 else
1950 count_str = str(LANG_PLAYLIST_INSERT_COUNT);
1952 display_playlist_count(count, count_str);
1954 result = add_directory_to_playlist(playlist, dirname, &position, queue,
1955 &count, recurse);
1956 fsync(playlist->control_fd);
1958 display_playlist_count(count, count_str);
1959 mpeg_flush_and_reload_tracks();
1961 return result;
1965 * Insert all tracks from specified playlist into dynamic playlist.
1967 int playlist_insert_playlist(struct playlist_info* playlist, char *filename,
1968 int position, bool queue)
1970 int fd;
1971 int max;
1972 char *temp_ptr;
1973 char *dir;
1974 char *count_str;
1975 char temp_buf[MAX_PATH+1];
1976 char trackname[MAX_PATH+1];
1977 int count = 0;
1978 int result = 0;
1980 if (!playlist)
1981 playlist = &current_playlist;
1983 if (check_control(playlist) < 0)
1985 splash(HZ*2, true, str(LANG_PLAYLIST_CONTROL_ACCESS_ERROR));
1986 return -1;
1989 fd = open(filename, O_RDONLY);
1990 if (fd < 0)
1992 splash(HZ*2, true, str(LANG_PLAYLIST_ACCESS_ERROR));
1993 return -1;
1996 /* we need the directory name for formatting purposes */
1997 dir = filename;
1999 temp_ptr = strrchr(filename+1,'/');
2000 if (temp_ptr)
2001 *temp_ptr = 0;
2002 else
2003 dir = "/";
2005 if (queue)
2006 count_str = str(LANG_PLAYLIST_QUEUE_COUNT);
2007 else
2008 count_str = str(LANG_PLAYLIST_INSERT_COUNT);
2010 display_playlist_count(count, count_str);
2012 while ((max = read_line(fd, temp_buf, sizeof(temp_buf))) > 0)
2014 /* user abort */
2015 if (button_get(false) == SETTINGS_CANCEL)
2016 break;
2018 if (temp_buf[0] != '#' && temp_buf[0] != '\0')
2020 int insert_pos;
2022 /* we need to format so that relative paths are correctly
2023 handled */
2024 if (format_track_path(trackname, temp_buf, sizeof(trackname), max,
2025 dir) < 0)
2027 result = -1;
2028 break;
2031 insert_pos = add_track_to_playlist(playlist, trackname, position,
2032 queue, -1);
2034 if (insert_pos < 0)
2036 result = -1;
2037 break;
2040 /* Make sure tracks are inserted in correct order if user
2041 requests INSERT_FIRST */
2042 if (position == PLAYLIST_INSERT_FIRST || position >= 0)
2043 position = insert_pos + 1;
2045 count++;
2047 if ((count%PLAYLIST_DISPLAY_COUNT) == 0)
2049 display_playlist_count(count, count_str);
2051 if (count == PLAYLIST_DISPLAY_COUNT)
2052 mpeg_flush_and_reload_tracks();
2056 /* let the other threads work */
2057 yield();
2060 close(fd);
2061 fsync(playlist->control_fd);
2063 if (temp_ptr)
2064 *temp_ptr = '/';
2066 display_playlist_count(count, count_str);
2067 mpeg_flush_and_reload_tracks();
2069 return result;
2073 * Delete track at specified index. If index is PLAYLIST_DELETE_CURRENT then
2074 * we want to delete the current playing track.
2076 int playlist_delete(struct playlist_info* playlist, int index)
2078 int result = 0;
2080 if (!playlist)
2081 playlist = &current_playlist;
2083 if (check_control(playlist) < 0)
2085 splash(HZ*2, true, str(LANG_PLAYLIST_CONTROL_ACCESS_ERROR));
2086 return -1;
2089 if (index == PLAYLIST_DELETE_CURRENT)
2090 index = playlist->index;
2092 result = remove_track_from_playlist(playlist, index, true);
2094 if (result != -1)
2095 mpeg_flush_and_reload_tracks();
2097 return result;
2101 * Move track at index to new_index. Tracks between the two are shifted
2102 * appropriately. Returns 0 on success and -1 on failure.
2104 int playlist_move(struct playlist_info* playlist, int index, int new_index)
2106 int result;
2107 int seek;
2108 bool control_file;
2109 bool queue;
2110 bool current = false;
2111 int r;
2112 char filename[MAX_PATH];
2114 if (!playlist)
2115 playlist = &current_playlist;
2117 if (check_control(playlist) < 0)
2119 splash(HZ*2, true, str(LANG_PLAYLIST_CONTROL_ACCESS_ERROR));
2120 return -1;
2123 if (index == new_index)
2124 return -1;
2126 if (index == playlist->index)
2127 /* Moving the current track */
2128 current = true;
2130 control_file = playlist->indices[index] & PLAYLIST_INSERT_TYPE_MASK;
2131 queue = playlist->indices[index] & PLAYLIST_QUEUE_MASK;
2132 seek = playlist->indices[index] & PLAYLIST_SEEK_MASK;
2134 if (get_filename(playlist, seek, control_file, filename,
2135 sizeof(filename)) < 0)
2136 return -1;
2138 /* Delete track from original position */
2139 result = remove_track_from_playlist(playlist, index, true);
2141 if (result != -1)
2143 /* We want to insert the track at the position that was specified by
2144 new_index. This may be different then new_index because of the
2145 shifting that occurred after the delete */
2146 r = rotate_index(playlist, new_index);
2148 if (r == 0)
2149 /* First index */
2150 new_index = PLAYLIST_PREPEND;
2151 else if (r == playlist->amount)
2152 /* Append */
2153 new_index = PLAYLIST_INSERT_LAST;
2154 else
2155 /* Calculate index of desired position */
2156 new_index = (r+playlist->first_index)%playlist->amount;
2158 result = add_track_to_playlist(playlist, filename, new_index, queue,
2159 -1);
2161 if (result != -1)
2163 if (current)
2165 /* Moved the current track */
2166 switch (new_index)
2168 case PLAYLIST_PREPEND:
2169 playlist->index = playlist->first_index;
2170 break;
2171 case PLAYLIST_INSERT_LAST:
2172 playlist->index = playlist->first_index - 1;
2173 if (playlist->index < 0)
2174 playlist->index += playlist->amount;
2175 break;
2176 default:
2177 playlist->index = new_index;
2178 break;
2182 fsync(playlist->control_fd);
2183 mpeg_flush_and_reload_tracks();
2187 return result;
2190 /* shuffle currently playing playlist */
2191 int playlist_randomise(struct playlist_info* playlist, unsigned int seed,
2192 bool start_current)
2194 int result;
2196 if (!playlist)
2197 playlist = &current_playlist;
2199 check_control(playlist);
2201 result = randomise_playlist(playlist, seed, start_current, true);
2203 if (result != -1)
2204 mpeg_flush_and_reload_tracks();
2206 return result;
2209 /* sort currently playing playlist */
2210 int playlist_sort(struct playlist_info* playlist, bool start_current)
2212 int result;
2214 if (!playlist)
2215 playlist = &current_playlist;
2217 check_control(playlist);
2219 result = sort_playlist(playlist, start_current, true);
2221 if (result != -1)
2222 mpeg_flush_and_reload_tracks();
2224 return result;
2227 /* returns true if playlist has been modified */
2228 bool playlist_modified(const struct playlist_info* playlist)
2230 if (!playlist)
2231 playlist = &current_playlist;
2233 if (playlist->shuffle_modified ||
2234 playlist->deleted ||
2235 playlist->num_inserted_tracks > 0)
2236 return true;
2238 return false;
2241 /* returns index of first track in playlist */
2242 int playlist_get_first_index(const struct playlist_info* playlist)
2244 if (!playlist)
2245 playlist = &current_playlist;
2247 return playlist->first_index;
2250 /* returns shuffle seed of playlist */
2251 int playlist_get_seed(const struct playlist_info* playlist)
2253 if (!playlist)
2254 playlist = &current_playlist;
2256 return playlist->seed;
2259 /* returns number of tracks in playlist (includes queued/inserted tracks) */
2260 int playlist_amount_ex(const struct playlist_info* playlist)
2262 if (!playlist)
2263 playlist = &current_playlist;
2265 return playlist->amount;
2268 /* returns full path of playlist (minus extension) */
2269 char *playlist_name(const struct playlist_info* playlist, char *buf,
2270 int buf_size)
2272 char *sep;
2274 if (!playlist)
2275 playlist = &current_playlist;
2277 snprintf(buf, buf_size, "%s", playlist->filename+playlist->dirlen);
2279 if (!buf[0])
2280 return NULL;
2282 /* Remove extension */
2283 sep = strrchr(buf, '.');
2284 if (sep)
2285 *sep = 0;
2287 return buf;
2290 /* returns the playlist filename */
2291 char *playlist_get_name(const struct playlist_info* playlist, char *buf,
2292 int buf_size)
2294 if (!playlist)
2295 playlist = &current_playlist;
2297 snprintf(buf, buf_size, "%s", playlist->filename);
2299 if (!buf[0])
2300 return NULL;
2302 return buf;
2305 /* Fills info structure with information about track at specified index.
2306 Returns 0 on success and -1 on failure */
2307 int playlist_get_track_info(struct playlist_info* playlist, int index,
2308 struct playlist_track_info* info)
2310 int seek;
2311 bool control_file;
2313 if (!playlist)
2314 playlist = &current_playlist;
2316 if (index < 0 || index >= playlist->amount)
2317 return -1;
2319 control_file = playlist->indices[index] & PLAYLIST_INSERT_TYPE_MASK;
2320 seek = playlist->indices[index] & PLAYLIST_SEEK_MASK;
2322 if (get_filename(playlist, seek, control_file, info->filename,
2323 sizeof(info->filename)) < 0)
2324 return -1;
2326 info->attr = 0;
2328 if (control_file)
2330 if (playlist->indices[index] & PLAYLIST_QUEUE_MASK)
2331 info->attr |= PLAYLIST_ATTR_QUEUED;
2332 else
2333 info->attr |= PLAYLIST_ATTR_INSERTED;
2336 info->index = index;
2337 info->display_index = rotate_index(playlist, index) + 1;
2339 return 0;
2342 /* save the current dynamic playlist to specified file */
2343 int playlist_save(struct playlist_info* playlist, char *filename)
2345 int fd;
2346 int i, index;
2347 int count = 0;
2348 char tmp_buf[MAX_PATH+1];
2349 int result = 0;
2351 if (!playlist)
2352 playlist = &current_playlist;
2354 if (playlist->amount <= 0)
2355 return -1;
2357 /* use current working directory as base for pathname */
2358 if (format_track_path(tmp_buf, filename, sizeof(tmp_buf),
2359 strlen(filename)+1, getcwd(NULL, -1)) < 0)
2360 return -1;
2362 fd = open(tmp_buf, O_CREAT|O_WRONLY|O_TRUNC);
2363 if (fd < 0)
2365 splash(HZ*2, true, str(LANG_PLAYLIST_ACCESS_ERROR));
2366 return -1;
2369 display_playlist_count(count, str(LANG_PLAYLIST_SAVE_COUNT));
2371 index = playlist->first_index;
2372 for (i=0; i<playlist->amount; i++)
2374 bool control_file;
2375 bool queue;
2376 int seek;
2378 /* user abort */
2379 if (button_get(false) == SETTINGS_CANCEL)
2380 break;
2382 control_file = playlist->indices[index] & PLAYLIST_INSERT_TYPE_MASK;
2383 queue = playlist->indices[index] & PLAYLIST_QUEUE_MASK;
2384 seek = playlist->indices[index] & PLAYLIST_SEEK_MASK;
2386 /* Don't save queued files */
2387 if (!queue)
2389 if (get_filename(playlist, seek, control_file, tmp_buf,
2390 MAX_PATH+1) < 0)
2392 result = -1;
2393 break;
2396 if (fprintf(fd, "%s\n", tmp_buf) < 0)
2398 splash(HZ*2, true, str(LANG_PLAYLIST_CONTROL_UPDATE_ERROR));
2399 result = -1;
2400 break;
2403 count++;
2405 if ((count % PLAYLIST_DISPLAY_COUNT) == 0)
2406 display_playlist_count(count, str(LANG_PLAYLIST_SAVE_COUNT));
2408 yield();
2411 index = (index+1)%playlist->amount;
2414 display_playlist_count(count, str(LANG_PLAYLIST_SAVE_COUNT));
2416 close(fd);
2418 return result;