Fix red.
[kugel-rb.git] / apps / playlist.c
blobd90de38f379630f58f0340c79d806d576ddfcd61
1 /***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
10 * Copyright (C) 2002 by wavey@wavey.org
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
20 ****************************************************************************/
23 Dynamic playlist design (based on design originally proposed by ricII)
25 There are two files associated with a dynamic playlist:
26 1. Playlist file : This file contains the initial songs in the playlist.
27 The file is created by the user and stored on the hard
28 drive. NOTE: If we are playing the contents of a
29 directory, there will be no playlist file.
30 2. Control file : This file is automatically created when a playlist is
31 started and contains all the commands done to it.
33 The first non-comment line in a control file must begin with
34 "P:VERSION:DIR:FILE" where VERSION is the playlist control file version,
35 DIR is the directory where the playlist is located and FILE is the
36 playlist filename. For dirplay, FILE will be empty. An empty playlist
37 will have both entries as null.
39 Control file commands:
40 a. Add track (A:<position>:<last position>:<path to track>)
41 - Insert a track at the specified position in the current
42 playlist. Last position is used to specify where last insertion
43 occurred.
44 b. Queue track (Q:<position>:<last position>:<path to track>)
45 - Queue a track at the specified position in the current
46 playlist. Queued tracks differ from added tracks in that they
47 are deleted from the playlist as soon as they are played and
48 they are not saved to disk as part of the playlist.
49 c. Delete track (D:<position>)
50 - Delete track from specified position in the current playlist.
51 d. Shuffle playlist (S:<seed>:<index>)
52 - Shuffle entire playlist with specified seed. The index
53 identifies the first index in the newly shuffled playlist
54 (needed for repeat mode).
55 e. Unshuffle playlist (U:<index>)
56 - Unshuffle entire playlist. The index identifies the first index
57 in the newly unshuffled playlist.
58 f. Reset last insert position (R)
59 - Needed so that insertions work properly after resume
61 Resume:
62 The only resume info that needs to be saved is the current index in the
63 playlist and the position in the track. When resuming, all the commands
64 in the control file will be reapplied so that the playlist indices are
65 exactly the same as before shutdown. To avoid unnecessary disk
66 accesses, the shuffle mode settings are also saved in settings and only
67 flushed to disk when required.
70 #include <stdio.h>
71 #include <stdlib.h>
72 #include <string.h>
73 #include <ctype.h>
74 #include "playlist.h"
75 #include "ata_idle_notify.h"
76 #include "file.h"
77 #include "action.h"
78 #include "dir.h"
79 #include "sprintf.h"
80 #include "debug.h"
81 #include "audio.h"
82 #include "lcd.h"
83 #include "kernel.h"
84 #include "settings.h"
85 #include "status.h"
86 #include "applimits.h"
87 #include "screens.h"
88 #include "buffer.h"
89 #include "misc.h"
90 #include "button.h"
91 #include "filetree.h"
92 #include "abrepeat.h"
93 #include "thread.h"
94 #include "usb.h"
95 #include "filetypes.h"
96 #ifdef HAVE_LCD_BITMAP
97 #include "icons.h"
98 #endif
99 #include "system.h"
101 #include "lang.h"
102 #include "talk.h"
103 #include "splash.h"
104 #include "rbunicode.h"
105 #include "root_menu.h"
107 #define PLAYLIST_CONTROL_FILE ROCKBOX_DIR "/.playlist_control"
108 #define PLAYLIST_CONTROL_FILE_VERSION 2
111 Each playlist index has a flag associated with it which identifies what
112 type of track it is. These flags are stored in the 4 high order bits of
113 the index.
115 NOTE: This limits the playlist file size to a max of 256M.
117 Bits 31-30:
118 00 = Playlist track
119 01 = Track was prepended into playlist
120 10 = Track was inserted into playlist
121 11 = Track was appended into playlist
122 Bit 29:
123 0 = Added track
124 1 = Queued track
125 Bit 28:
126 0 = Track entry is valid
127 1 = Track does not exist on disk and should be skipped
129 #define PLAYLIST_SEEK_MASK 0x0FFFFFFF
130 #define PLAYLIST_INSERT_TYPE_MASK 0xC0000000
131 #define PLAYLIST_QUEUE_MASK 0x20000000
133 #define PLAYLIST_INSERT_TYPE_PREPEND 0x40000000
134 #define PLAYLIST_INSERT_TYPE_INSERT 0x80000000
135 #define PLAYLIST_INSERT_TYPE_APPEND 0xC0000000
137 #define PLAYLIST_QUEUED 0x20000000
138 #define PLAYLIST_SKIPPED 0x10000000
140 struct directory_search_context {
141 struct playlist_info* playlist;
142 int position;
143 bool queue;
144 int count;
147 static struct playlist_info current_playlist;
148 static char now_playing[MAX_PATH+1];
150 static void empty_playlist(struct playlist_info* playlist, bool resume);
151 static void new_playlist(struct playlist_info* playlist, const char *dir,
152 const char *file);
153 static void create_control(struct playlist_info* playlist);
154 static int check_control(struct playlist_info* playlist);
155 static int recreate_control(struct playlist_info* playlist);
156 static void update_playlist_filename(struct playlist_info* playlist,
157 const char *dir, const char *file);
158 static int add_indices_to_playlist(struct playlist_info* playlist,
159 char* buffer, size_t buflen);
160 static int add_track_to_playlist(struct playlist_info* playlist,
161 const char *filename, int position,
162 bool queue, int seek_pos);
163 static int directory_search_callback(char* filename, void* context);
164 static int remove_track_from_playlist(struct playlist_info* playlist,
165 int position, bool write);
166 static int randomise_playlist(struct playlist_info* playlist,
167 unsigned int seed, bool start_current,
168 bool write);
169 static int sort_playlist(struct playlist_info* playlist, bool start_current,
170 bool write);
171 static int get_next_index(const struct playlist_info* playlist, int steps,
172 int repeat_mode);
173 static void find_and_set_playlist_index(struct playlist_info* playlist,
174 unsigned int seek);
175 static int compare(const void* p1, const void* p2);
176 static int get_filename(struct playlist_info* playlist, int index, int seek,
177 bool control_file, char *buf, int buf_length);
178 static int get_next_directory(char *dir);
179 static int get_next_dir(char *dir, bool is_forward, bool recursion);
180 static int get_previous_directory(char *dir);
181 static int check_subdir_for_music(char *dir, const char *subdir, bool recurse);
182 static int format_track_path(char *dest, char *src, int buf_length, int max,
183 const char *dir);
184 static void display_playlist_count(int count, const unsigned char *fmt,
185 bool final);
186 static void display_buffer_full(void);
187 static int flush_cached_control(struct playlist_info* playlist);
188 static int update_control(struct playlist_info* playlist,
189 enum playlist_command command, int i1, int i2,
190 const char* s1, const char* s2, void* data);
191 static void sync_control(struct playlist_info* playlist, bool force);
192 static int rotate_index(const struct playlist_info* playlist, int index);
194 #ifdef HAVE_DIRCACHE
195 #define PLAYLIST_LOAD_POINTERS 1
197 static struct event_queue playlist_queue;
198 static long playlist_stack[(DEFAULT_STACK_SIZE + 0x800)/sizeof(long)];
199 static const char playlist_thread_name[] = "playlist cachectrl";
200 #endif
202 /* Check if the filename suggests M3U or M3U8 format. */
203 static bool is_m3u8(const char* filename)
205 int len = strlen(filename);
207 /* Default to M3U8 unless explicitly told otherwise. */
208 return !(len > 4 && strcasecmp(&filename[len - 4], ".m3u") == 0);
212 /* Convert a filename in an M3U playlist to UTF-8.
214 * buf - the filename to convert; can contain more than one line from the
215 * playlist.
216 * buf_len - amount of buf that is used.
217 * buf_max - total size of buf.
218 * temp - temporary conversion buffer, at least buf_max bytes.
220 * Returns the length of the converted filename.
222 static int convert_m3u(char* buf, int buf_len, int buf_max, char* temp)
224 int i = 0;
225 char* dest;
227 /* Locate EOL. */
228 while ((buf[i] != '\n') && (buf[i] != '\r') && (i < buf_len))
230 i++;
233 /* Work back killing white space. */
234 while ((i > 0) && isspace(buf[i - 1]))
236 i--;
239 buf_len = i;
240 dest = temp;
242 /* Convert char by char, so as to not overflow temp (iso_decode should
243 * preferably handle this). No more than 4 bytes should be generated for
244 * each input char.
246 for (i = 0; i < buf_len && dest < (temp + buf_max - 4); i++)
248 dest = iso_decode(&buf[i], dest, -1, 1);
251 *dest = 0;
252 strcpy(buf, temp);
253 return dest - temp;
257 * remove any files and indices associated with the playlist
259 static void empty_playlist(struct playlist_info* playlist, bool resume)
261 playlist->filename[0] = '\0';
262 playlist->utf8 = true;
264 if(playlist->fd >= 0)
265 /* If there is an already open playlist, close it. */
266 close(playlist->fd);
267 playlist->fd = -1;
269 if(playlist->control_fd >= 0)
270 close(playlist->control_fd);
271 playlist->control_fd = -1;
272 playlist->control_created = false;
274 playlist->in_ram = false;
276 if (playlist->buffer)
277 playlist->buffer[0] = 0;
279 playlist->buffer_end_pos = 0;
281 playlist->index = 0;
282 playlist->first_index = 0;
283 playlist->amount = 0;
284 playlist->last_insert_pos = -1;
285 playlist->seed = 0;
286 playlist->shuffle_modified = false;
287 playlist->deleted = false;
288 playlist->num_inserted_tracks = 0;
289 playlist->started = false;
291 playlist->num_cached = 0;
292 playlist->pending_control_sync = false;
294 if (!resume && playlist->current)
296 /* start with fresh playlist control file when starting new
297 playlist */
298 create_control(playlist);
303 * Initialize a new playlist for viewing/editing/playing. dir is the
304 * directory where the playlist is located and file is the filename.
306 static void new_playlist(struct playlist_info* playlist, const char *dir,
307 const char *file)
309 const char *fileused = file;
310 const char *dirused = dir;
311 empty_playlist(playlist, false);
313 if (!fileused)
315 fileused = "";
317 if (dirused && playlist->current) /* !current cannot be in_ram */
318 playlist->in_ram = true;
319 else
320 dirused = ""; /* empty playlist */
323 update_playlist_filename(playlist, dirused, fileused);
325 if (playlist->control_fd >= 0)
327 update_control(playlist, PLAYLIST_COMMAND_PLAYLIST,
328 PLAYLIST_CONTROL_FILE_VERSION, -1, dirused, fileused, NULL);
329 sync_control(playlist, false);
334 * create control file for playlist
336 static void create_control(struct playlist_info* playlist)
338 playlist->control_fd = open(playlist->control_filename,
339 O_CREAT|O_RDWR|O_TRUNC);
340 if (playlist->control_fd < 0)
342 if (check_rockboxdir())
344 cond_talk_ids_fq(LANG_PLAYLIST_CONTROL_ACCESS_ERROR);
345 splashf(HZ*2, (unsigned char *)"%s (%d)",
346 str(LANG_PLAYLIST_CONTROL_ACCESS_ERROR),
347 playlist->control_fd);
349 playlist->control_created = false;
351 else
353 playlist->control_created = true;
358 * validate the control file. This may include creating/initializing it if
359 * necessary;
361 static int check_control(struct playlist_info* playlist)
363 if (!playlist->control_created)
365 create_control(playlist);
367 if (playlist->control_fd >= 0)
369 char* dir = playlist->filename;
370 char* file = playlist->filename+playlist->dirlen;
371 char c = playlist->filename[playlist->dirlen-1];
373 playlist->filename[playlist->dirlen-1] = '\0';
375 update_control(playlist, PLAYLIST_COMMAND_PLAYLIST,
376 PLAYLIST_CONTROL_FILE_VERSION, -1, dir, file, NULL);
377 sync_control(playlist, false);
378 playlist->filename[playlist->dirlen-1] = c;
382 if (playlist->control_fd < 0)
383 return -1;
385 return 0;
389 * recreate the control file based on current playlist entries
391 static int recreate_control(struct playlist_info* playlist)
393 char temp_file[MAX_PATH+1];
394 int temp_fd = -1;
395 int i;
396 int result = 0;
398 if(playlist->control_fd >= 0)
400 char* dir = playlist->filename;
401 char* file = playlist->filename+playlist->dirlen;
402 char c = playlist->filename[playlist->dirlen-1];
404 close(playlist->control_fd);
406 snprintf(temp_file, sizeof(temp_file), "%s_temp",
407 playlist->control_filename);
409 if (rename(playlist->control_filename, temp_file) < 0)
410 return -1;
412 temp_fd = open(temp_file, O_RDONLY);
413 if (temp_fd < 0)
414 return -1;
416 playlist->control_fd = open(playlist->control_filename,
417 O_CREAT|O_RDWR|O_TRUNC);
418 if (playlist->control_fd < 0)
419 return -1;
421 playlist->filename[playlist->dirlen-1] = '\0';
423 /* cannot call update_control() because of mutex */
424 result = fdprintf(playlist->control_fd, "P:%d:%s:%s\n",
425 PLAYLIST_CONTROL_FILE_VERSION, dir, file);
427 playlist->filename[playlist->dirlen-1] = c;
429 if (result < 0)
431 close(temp_fd);
432 return result;
436 playlist->seed = 0;
437 playlist->shuffle_modified = false;
438 playlist->deleted = false;
439 playlist->num_inserted_tracks = 0;
441 for (i=0; i<playlist->amount; i++)
443 if (playlist->indices[i] & PLAYLIST_INSERT_TYPE_MASK)
445 bool queue = playlist->indices[i] & PLAYLIST_QUEUE_MASK;
446 char inserted_file[MAX_PATH+1];
448 lseek(temp_fd, playlist->indices[i] & PLAYLIST_SEEK_MASK,
449 SEEK_SET);
450 read_line(temp_fd, inserted_file, sizeof(inserted_file));
452 result = fdprintf(playlist->control_fd, "%c:%d:%d:",
453 queue?'Q':'A', i, playlist->last_insert_pos);
454 if (result > 0)
456 /* save the position in file where name is written */
457 int seek_pos = lseek(playlist->control_fd, 0, SEEK_CUR);
459 result = fdprintf(playlist->control_fd, "%s\n",
460 inserted_file);
462 playlist->indices[i] =
463 (playlist->indices[i] & ~PLAYLIST_SEEK_MASK) | seek_pos;
466 if (result < 0)
467 break;
469 playlist->num_inserted_tracks++;
473 close(temp_fd);
474 remove(temp_file);
475 fsync(playlist->control_fd);
477 if (result < 0)
478 return result;
480 return 0;
484 * store directory and name of playlist file
486 static void update_playlist_filename(struct playlist_info* playlist,
487 const char *dir, const char *file)
489 char *sep="";
490 int dirlen = strlen(dir);
492 playlist->utf8 = is_m3u8(file);
494 /* If the dir does not end in trailing slash, we use a separator.
495 Otherwise we don't. */
496 if('/' != dir[dirlen-1])
498 sep="/";
499 dirlen++;
502 playlist->dirlen = dirlen;
504 snprintf(playlist->filename, sizeof(playlist->filename),
505 "%s%s%s", dir, sep, file);
509 * calculate track offsets within a playlist file
511 static int add_indices_to_playlist(struct playlist_info* playlist,
512 char* buffer, size_t buflen)
514 unsigned int nread;
515 unsigned int i = 0;
516 unsigned int count = 0;
517 bool store_index;
518 unsigned char *p;
519 int result = 0;
521 if(-1 == playlist->fd)
522 playlist->fd = open_utf8(playlist->filename, O_RDONLY);
523 if(playlist->fd < 0)
524 return -1; /* failure */
525 if((i = lseek(playlist->fd, 0, SEEK_CUR)) > 0)
526 playlist->utf8 = true; /* Override any earlier indication. */
528 splash(0, ID2P(LANG_WAIT));
530 if (!buffer)
532 /* use mp3 buffer for maximum load speed */
533 audio_stop();
534 #if CONFIG_CODEC != SWCODEC
535 talk_buffer_steal(); /* we use the mp3 buffer, need to tell */
536 buflen = (audiobufend - audiobuf);
537 buffer = (char *)audiobuf;
538 #else
539 buffer = (char *)audio_get_buffer(false, &buflen);
540 #endif
543 store_index = true;
545 while(1)
547 nread = read(playlist->fd, buffer, buflen);
548 /* Terminate on EOF */
549 if(nread <= 0)
550 break;
552 p = (unsigned char *)buffer;
554 for(count=0; count < nread; count++,p++) {
556 /* Are we on a new line? */
557 if((*p == '\n') || (*p == '\r'))
559 store_index = true;
561 else if(store_index)
563 store_index = false;
565 if(*p != '#')
567 if ( playlist->amount >= playlist->max_playlist_size ) {
568 display_buffer_full();
569 result = -1;
570 goto exit;
573 /* Store a new entry */
574 playlist->indices[ playlist->amount ] = i+count;
575 #ifdef HAVE_DIRCACHE
576 if (playlist->filenames)
577 playlist->filenames[ playlist->amount ] = NULL;
578 #endif
579 playlist->amount++;
584 i+= count;
587 exit:
588 #ifdef HAVE_DIRCACHE
589 queue_post(&playlist_queue, PLAYLIST_LOAD_POINTERS, 0);
590 #endif
592 return result;
596 * Utility function to create a new playlist, fill it with the next or
597 * previous directory, shuffle it if needed, and start playback.
598 * If play_last is true and direction zero or negative, start playing
599 * the last file in the directory, otherwise start playing the first.
601 static int create_and_play_dir(int direction, bool play_last)
603 char dir[MAX_PATH + 1];
604 int res;
605 int index = -1;
607 if(direction > 0)
608 res = get_next_directory(dir);
609 else
610 res = get_previous_directory(dir);
612 if (!res)
614 if (playlist_create(dir, NULL) != -1)
616 ft_build_playlist(tree_get_context(), 0);
618 if (global_settings.playlist_shuffle)
619 playlist_shuffle(current_tick, -1);
621 if (play_last && direction <= 0)
622 index = current_playlist.amount - 1;
623 else
624 index = 0;
626 #if (CONFIG_CODEC != SWCODEC)
627 playlist_start(index, 0);
628 #endif
631 /* we've overwritten the dircache when getting the next/previous dir,
632 so the tree browser context will need to be reloaded */
633 reload_directory();
636 return index;
640 * Removes all tracks, from the playlist, leaving the presently playing
641 * track queued.
643 int playlist_remove_all_tracks(struct playlist_info *playlist)
645 int result;
647 if (playlist == NULL)
648 playlist = &current_playlist;
650 while (playlist->index > 0)
651 if ((result = remove_track_from_playlist(playlist, 0, true)) < 0)
652 return result;
654 while (playlist->amount > 1)
655 if ((result = remove_track_from_playlist(playlist, 1, true)) < 0)
656 return result;
658 if (playlist->amount == 1) {
659 playlist->indices[0] |= PLAYLIST_QUEUED;
662 return 0;
667 * Add track to playlist at specified position. There are seven special
668 * positions that can be specified:
669 * PLAYLIST_PREPEND - Add track at beginning of playlist
670 * PLAYLIST_INSERT - Add track after current song. NOTE: If
671 * there are already inserted tracks then track
672 * is added to the end of the insertion list
673 * PLAYLIST_INSERT_FIRST - Add track immediately after current song, no
674 * matter what other tracks have been inserted
675 * PLAYLIST_INSERT_LAST - Add track to end of playlist
676 * PLAYLIST_INSERT_SHUFFLED - Add track at some random point between the
677 * current playing track and end of playlist
678 * PLAYLIST_INSERT_LAST_SHUFFLED - Add tracks in random order to the end of
679 * the playlist.
680 * PLAYLIST_REPLACE - Erase current playlist, Cue the current track
681 * and inster this track at the end.
683 static int add_track_to_playlist(struct playlist_info* playlist,
684 const char *filename, int position,
685 bool queue, int seek_pos)
687 int insert_position, orig_position;
688 unsigned long flags = PLAYLIST_INSERT_TYPE_INSERT;
689 int i;
691 insert_position = orig_position = position;
693 if (playlist->amount >= playlist->max_playlist_size)
695 display_buffer_full();
696 return -1;
699 switch (position)
701 case PLAYLIST_PREPEND:
702 position = insert_position = playlist->first_index;
703 break;
704 case PLAYLIST_INSERT:
705 /* if there are already inserted tracks then add track to end of
706 insertion list else add after current playing track */
707 if (playlist->last_insert_pos >= 0 &&
708 playlist->last_insert_pos < playlist->amount &&
709 (playlist->indices[playlist->last_insert_pos]&
710 PLAYLIST_INSERT_TYPE_MASK) == PLAYLIST_INSERT_TYPE_INSERT)
711 position = insert_position = playlist->last_insert_pos+1;
712 else if (playlist->amount > 0)
713 position = insert_position = playlist->index + 1;
714 else
715 position = insert_position = 0;
717 playlist->last_insert_pos = position;
718 break;
719 case PLAYLIST_INSERT_FIRST:
720 if (playlist->amount > 0)
721 position = insert_position = playlist->index + 1;
722 else
723 position = insert_position = 0;
725 playlist->last_insert_pos = position;
726 break;
727 case PLAYLIST_INSERT_LAST:
728 if (playlist->first_index > 0)
729 position = insert_position = playlist->first_index;
730 else
731 position = insert_position = playlist->amount;
733 playlist->last_insert_pos = position;
734 break;
735 case PLAYLIST_INSERT_SHUFFLED:
737 if (playlist->started)
739 int offset;
740 int n = playlist->amount -
741 rotate_index(playlist, playlist->index);
743 if (n > 0)
744 offset = rand() % n;
745 else
746 offset = 0;
748 position = playlist->index + offset + 1;
749 if (position >= playlist->amount)
750 position -= playlist->amount;
752 insert_position = position;
754 else
755 position = insert_position = (rand() % (playlist->amount+1));
756 break;
758 case PLAYLIST_INSERT_LAST_SHUFFLED:
760 position = insert_position = playlist->last_shuffled_start +
761 rand() % (playlist->amount - playlist->last_shuffled_start + 1);
762 break;
764 case PLAYLIST_REPLACE:
765 if (playlist_remove_all_tracks(playlist) < 0)
766 return -1;
768 playlist->last_insert_pos = position = insert_position = playlist->index + 1;
769 break;
772 if (queue)
773 flags |= PLAYLIST_QUEUED;
775 /* shift indices so that track can be added */
776 for (i=playlist->amount; i>insert_position; i--)
778 playlist->indices[i] = playlist->indices[i-1];
779 #ifdef HAVE_DIRCACHE
780 if (playlist->filenames)
781 playlist->filenames[i] = playlist->filenames[i-1];
782 #endif
785 /* update stored indices if needed */
787 if (orig_position < 0)
789 if (playlist->amount > 0 && insert_position <= playlist->index &&
790 playlist->started)
791 playlist->index++;
793 if (playlist->amount > 0 && insert_position <= playlist->first_index &&
794 orig_position != PLAYLIST_PREPEND && playlist->started)
795 playlist->first_index++;
798 if (insert_position < playlist->last_insert_pos ||
799 (insert_position == playlist->last_insert_pos && position < 0))
800 playlist->last_insert_pos++;
802 if (seek_pos < 0 && playlist->control_fd >= 0)
804 int result = update_control(playlist,
805 (queue?PLAYLIST_COMMAND_QUEUE:PLAYLIST_COMMAND_ADD), position,
806 playlist->last_insert_pos, filename, NULL, &seek_pos);
808 if (result < 0)
809 return result;
812 playlist->indices[insert_position] = flags | seek_pos;
814 #ifdef HAVE_DIRCACHE
815 if (playlist->filenames)
816 playlist->filenames[insert_position] = NULL;
817 #endif
819 playlist->amount++;
820 playlist->num_inserted_tracks++;
822 return insert_position;
826 * Callback for playlist_directory_tracksearch to insert track into
827 * playlist.
829 static int directory_search_callback(char* filename, void* context)
831 struct directory_search_context* c =
832 (struct directory_search_context*) context;
833 int insert_pos;
835 insert_pos = add_track_to_playlist(c->playlist, filename, c->position,
836 c->queue, -1);
838 if (insert_pos < 0)
839 return -1;
841 (c->count)++;
843 /* Make sure tracks are inserted in correct order if user requests
844 INSERT_FIRST */
845 if (c->position == PLAYLIST_INSERT_FIRST || c->position >= 0)
846 c->position = insert_pos + 1;
848 if (((c->count)%PLAYLIST_DISPLAY_COUNT) == 0)
850 unsigned char* count_str;
852 if (c->queue)
853 count_str = ID2P(LANG_PLAYLIST_QUEUE_COUNT);
854 else
855 count_str = ID2P(LANG_PLAYLIST_INSERT_COUNT);
857 display_playlist_count(c->count, count_str, false);
859 if ((c->count) == PLAYLIST_DISPLAY_COUNT &&
860 (audio_status() & AUDIO_STATUS_PLAY) &&
861 c->playlist->started)
862 audio_flush_and_reload_tracks();
865 return 0;
869 * remove track at specified position
871 static int remove_track_from_playlist(struct playlist_info* playlist,
872 int position, bool write)
874 int i;
875 bool inserted;
877 if (playlist->amount <= 0)
878 return -1;
880 inserted = playlist->indices[position] & PLAYLIST_INSERT_TYPE_MASK;
882 /* shift indices now that track has been removed */
883 for (i=position; i<playlist->amount; i++)
885 playlist->indices[i] = playlist->indices[i+1];
886 #ifdef HAVE_DIRCACHE
887 if (playlist->filenames)
888 playlist->filenames[i] = playlist->filenames[i+1];
889 #endif
892 playlist->amount--;
894 if (inserted)
895 playlist->num_inserted_tracks--;
896 else
897 playlist->deleted = true;
899 /* update stored indices if needed */
900 if (position < playlist->index)
901 playlist->index--;
903 if (position < playlist->first_index)
905 playlist->first_index--;
908 if (position <= playlist->last_insert_pos)
909 playlist->last_insert_pos--;
911 if (write && playlist->control_fd >= 0)
913 int result = update_control(playlist, PLAYLIST_COMMAND_DELETE,
914 position, -1, NULL, NULL, NULL);
916 if (result < 0)
917 return result;
919 sync_control(playlist, false);
922 return 0;
926 * randomly rearrange the array of indices for the playlist. If start_current
927 * is true then update the index to the new index of the current playing track
929 static int randomise_playlist(struct playlist_info* playlist,
930 unsigned int seed, bool start_current,
931 bool write)
933 int count;
934 int candidate;
935 long store;
936 unsigned int current = playlist->indices[playlist->index];
938 /* seed 0 is used to identify sorted playlist for resume purposes */
939 if (seed == 0)
940 seed = 1;
942 /* seed with the given seed */
943 srand(seed);
945 /* randomise entire indices list */
946 for(count = playlist->amount - 1; count >= 0; count--)
948 /* the rand is from 0 to RAND_MAX, so adjust to our value range */
949 candidate = rand() % (count + 1);
951 /* now swap the values at the 'count' and 'candidate' positions */
952 store = playlist->indices[candidate];
953 playlist->indices[candidate] = playlist->indices[count];
954 playlist->indices[count] = store;
955 #ifdef HAVE_DIRCACHE
956 if (playlist->filenames)
958 store = (long)playlist->filenames[candidate];
959 playlist->filenames[candidate] = playlist->filenames[count];
960 playlist->filenames[count] = (struct dircache_entry *)store;
962 #endif
965 if (start_current)
966 find_and_set_playlist_index(playlist, current);
968 /* indices have been moved so last insert position is no longer valid */
969 playlist->last_insert_pos = -1;
971 playlist->seed = seed;
972 if (playlist->num_inserted_tracks > 0 || playlist->deleted)
973 playlist->shuffle_modified = true;
975 if (write)
977 update_control(playlist, PLAYLIST_COMMAND_SHUFFLE, seed,
978 playlist->first_index, NULL, NULL, NULL);
981 return 0;
985 * Sort the array of indices for the playlist. If start_current is true then
986 * set the index to the new index of the current song.
987 * Also while going to unshuffled mode set the first_index to 0.
989 static int sort_playlist(struct playlist_info* playlist, bool start_current,
990 bool write)
992 unsigned int current = playlist->indices[playlist->index];
994 if (playlist->amount > 0)
995 qsort(playlist->indices, playlist->amount,
996 sizeof(playlist->indices[0]), compare);
998 #ifdef HAVE_DIRCACHE
999 /** We need to re-check the song names from disk because qsort can't
1000 * sort two arrays at once :/
1001 * FIXME: Please implement a better way to do this. */
1002 memset(playlist->filenames, 0, playlist->max_playlist_size * sizeof(int));
1003 queue_post(&playlist_queue, PLAYLIST_LOAD_POINTERS, 0);
1004 #endif
1006 if (start_current)
1007 find_and_set_playlist_index(playlist, current);
1009 /* indices have been moved so last insert position is no longer valid */
1010 playlist->last_insert_pos = -1;
1012 if (!playlist->num_inserted_tracks && !playlist->deleted)
1013 playlist->shuffle_modified = false;
1014 if (write && playlist->control_fd >= 0)
1016 playlist->first_index = 0;
1017 update_control(playlist, PLAYLIST_COMMAND_UNSHUFFLE,
1018 playlist->first_index, -1, NULL, NULL, NULL);
1021 return 0;
1024 /* Calculate how many steps we have to really step when skipping entries
1025 * marked as bad.
1027 static int calculate_step_count(const struct playlist_info *playlist, int steps)
1029 int i, count, direction;
1030 int index;
1031 int stepped_count = 0;
1033 if (steps < 0)
1035 direction = -1;
1036 count = -steps;
1038 else
1040 direction = 1;
1041 count = steps;
1044 index = playlist->index;
1045 i = 0;
1046 do {
1047 /* Boundary check */
1048 if (index < 0)
1049 index += playlist->amount;
1050 if (index >= playlist->amount)
1051 index -= playlist->amount;
1053 /* Check if we found a bad entry. */
1054 if (playlist->indices[index] & PLAYLIST_SKIPPED)
1056 steps += direction;
1057 /* Are all entries bad? */
1058 if (stepped_count++ > playlist->amount)
1059 break ;
1061 else
1062 i++;
1064 index += direction;
1065 } while (i <= count);
1067 return steps;
1070 /* Marks the index of the track to be skipped that is "steps" away from
1071 * current playing track.
1073 void playlist_skip_entry(struct playlist_info *playlist, int steps)
1075 int index;
1077 if (playlist == NULL)
1078 playlist = &current_playlist;
1080 /* need to account for already skipped tracks */
1081 steps = calculate_step_count(playlist, steps);
1083 index = playlist->index + steps;
1084 if (index < 0)
1085 index += playlist->amount;
1086 else if (index >= playlist->amount)
1087 index -= playlist->amount;
1089 playlist->indices[index] |= PLAYLIST_SKIPPED;
1093 * returns the index of the track that is "steps" away from current playing
1094 * track.
1096 static int get_next_index(const struct playlist_info* playlist, int steps,
1097 int repeat_mode)
1099 int current_index = playlist->index;
1100 int next_index = -1;
1102 if (playlist->amount <= 0)
1103 return -1;
1105 if (repeat_mode == -1)
1106 repeat_mode = global_settings.repeat_mode;
1108 if (repeat_mode == REPEAT_SHUFFLE && playlist->amount <= 1)
1109 repeat_mode = REPEAT_ALL;
1111 steps = calculate_step_count(playlist, steps);
1112 switch (repeat_mode)
1114 case REPEAT_SHUFFLE:
1115 /* Treat repeat shuffle just like repeat off. At end of playlist,
1116 play will be resumed in playlist_next() */
1117 case REPEAT_OFF:
1119 current_index = rotate_index(playlist, current_index);
1120 next_index = current_index+steps;
1121 if ((next_index < 0) || (next_index >= playlist->amount))
1122 next_index = -1;
1123 else
1124 next_index = (next_index+playlist->first_index) %
1125 playlist->amount;
1127 break;
1130 case REPEAT_ONE:
1131 #ifdef AB_REPEAT_ENABLE
1132 case REPEAT_AB:
1133 #endif
1134 next_index = current_index;
1135 break;
1137 case REPEAT_ALL:
1138 default:
1140 next_index = (current_index+steps) % playlist->amount;
1141 while (next_index < 0)
1142 next_index += playlist->amount;
1144 if (steps >= playlist->amount)
1146 int i, index;
1148 index = next_index;
1149 next_index = -1;
1151 /* second time around so skip the queued files */
1152 for (i=0; i<playlist->amount; i++)
1154 if (playlist->indices[index] & PLAYLIST_QUEUE_MASK)
1155 index = (index+1) % playlist->amount;
1156 else
1158 next_index = index;
1159 break;
1163 break;
1167 /* No luck if the whole playlist was bad. */
1168 if (playlist->indices[next_index] & PLAYLIST_SKIPPED)
1169 return -1;
1171 return next_index;
1175 * Search for the seek track and set appropriate indices. Used after shuffle
1176 * to make sure the current index is still pointing to correct track.
1178 static void find_and_set_playlist_index(struct playlist_info* playlist,
1179 unsigned int seek)
1181 int i;
1183 /* Set the index to the current song */
1184 for (i=0; i<playlist->amount; i++)
1186 if (playlist->indices[i] == seek)
1188 playlist->index = playlist->first_index = i;
1190 break;
1196 * used to sort track indices. Sort order is as follows:
1197 * 1. Prepended tracks (in prepend order)
1198 * 2. Playlist/directory tracks (in playlist order)
1199 * 3. Inserted/Appended tracks (in insert order)
1201 static int compare(const void* p1, const void* p2)
1203 unsigned long* e1 = (unsigned long*) p1;
1204 unsigned long* e2 = (unsigned long*) p2;
1205 unsigned long flags1 = *e1 & PLAYLIST_INSERT_TYPE_MASK;
1206 unsigned long flags2 = *e2 & PLAYLIST_INSERT_TYPE_MASK;
1208 if (flags1 == flags2)
1209 return (*e1 & PLAYLIST_SEEK_MASK) - (*e2 & PLAYLIST_SEEK_MASK);
1210 else if (flags1 == PLAYLIST_INSERT_TYPE_PREPEND ||
1211 flags2 == PLAYLIST_INSERT_TYPE_APPEND)
1212 return -1;
1213 else if (flags1 == PLAYLIST_INSERT_TYPE_APPEND ||
1214 flags2 == PLAYLIST_INSERT_TYPE_PREPEND)
1215 return 1;
1216 else if (flags1 && flags2)
1217 return (*e1 & PLAYLIST_SEEK_MASK) - (*e2 & PLAYLIST_SEEK_MASK);
1218 else
1219 return *e1 - *e2;
1222 #ifdef HAVE_DIRCACHE
1224 * Thread to update filename pointers to dircache on background
1225 * without affecting playlist load up performance. This thread also flushes
1226 * any pending control commands when the disk spins up.
1228 static void playlist_flush_callback(void *param)
1230 (void)param;
1231 struct playlist_info *playlist;
1232 playlist = &current_playlist;
1233 if (playlist->control_fd >= 0)
1235 if (playlist->num_cached > 0)
1237 mutex_lock(&playlist->control_mutex);
1238 flush_cached_control(playlist);
1239 mutex_unlock(&playlist->control_mutex);
1241 sync_control(playlist, true);
1245 static void playlist_thread(void)
1247 struct queue_event ev;
1248 bool dirty_pointers = false;
1249 static char tmp[MAX_PATH+1];
1251 struct playlist_info *playlist;
1252 int index;
1253 int seek;
1254 bool control_file;
1256 int sleep_time = 5;
1258 #ifdef HAVE_DISK_STORAGE
1259 if (global_settings.disk_spindown > 1 &&
1260 global_settings.disk_spindown <= 5)
1261 sleep_time = global_settings.disk_spindown - 1;
1262 #endif
1264 while (1)
1266 queue_wait_w_tmo(&playlist_queue, &ev, HZ*sleep_time);
1268 switch (ev.id)
1270 case PLAYLIST_LOAD_POINTERS:
1271 dirty_pointers = true;
1272 break ;
1274 /* Start the background scanning after either the disk spindown
1275 timeout or 5s, whichever is less */
1276 case SYS_TIMEOUT:
1277 playlist = &current_playlist;
1278 if (playlist->control_fd >= 0)
1280 if (playlist->num_cached > 0)
1281 register_storage_idle_func(playlist_flush_callback);
1284 if (!dirty_pointers)
1285 break ;
1287 if (!dircache_is_enabled() || !playlist->filenames
1288 || playlist->amount <= 0)
1289 break ;
1291 #ifdef HAVE_ADJUSTABLE_CPU_FREQ
1292 cpu_boost(true);
1293 #endif
1294 for (index = 0; index < playlist->amount
1295 && queue_empty(&playlist_queue); index++)
1297 /* Process only pointers that are not already loaded. */
1298 if (playlist->filenames[index])
1299 continue ;
1301 control_file = playlist->indices[index] & PLAYLIST_INSERT_TYPE_MASK;
1302 seek = playlist->indices[index] & PLAYLIST_SEEK_MASK;
1304 /* Load the filename from playlist file. */
1305 if (get_filename(playlist, index, seek, control_file, tmp,
1306 sizeof(tmp)) < 0)
1307 break ;
1309 /* Set the dircache entry pointer. */
1310 playlist->filenames[index] = dircache_get_entry_ptr(tmp);
1312 /* And be on background so user doesn't notice any delays. */
1313 yield();
1316 #ifdef HAVE_ADJUSTABLE_CPU_FREQ
1317 cpu_boost(false);
1318 #endif
1319 dirty_pointers = false;
1320 break ;
1322 #ifndef SIMULATOR
1323 case SYS_USB_CONNECTED:
1324 usb_acknowledge(SYS_USB_CONNECTED_ACK);
1325 usb_wait_for_disconnect(&playlist_queue);
1326 break ;
1327 #endif
1331 #endif
1334 * gets pathname for track at seek index
1336 static int get_filename(struct playlist_info* playlist, int index, int seek,
1337 bool control_file, char *buf, int buf_length)
1339 int fd;
1340 int max = -1;
1341 char tmp_buf[MAX_PATH+1];
1342 char dir_buf[MAX_PATH+1];
1343 bool utf8 = playlist->utf8;
1345 if (buf_length > MAX_PATH+1)
1346 buf_length = MAX_PATH+1;
1348 #ifdef HAVE_DIRCACHE
1349 if (dircache_is_enabled() && playlist->filenames)
1351 if (playlist->filenames[index] != NULL)
1353 dircache_copy_path(playlist->filenames[index], tmp_buf, sizeof(tmp_buf)-1);
1354 max = strlen(tmp_buf);
1357 #else
1358 (void)index;
1359 #endif
1361 if (playlist->in_ram && !control_file && max < 0)
1363 max = strlcpy(tmp_buf, &playlist->buffer[seek], sizeof(tmp_buf));
1365 else if (max < 0)
1367 mutex_lock(&playlist->control_mutex);
1369 if (control_file)
1371 fd = playlist->control_fd;
1372 utf8 = true;
1374 else
1376 if(-1 == playlist->fd)
1377 playlist->fd = open(playlist->filename, O_RDONLY);
1379 fd = playlist->fd;
1382 if(-1 != fd)
1385 if (lseek(fd, seek, SEEK_SET) != seek)
1386 max = -1;
1387 else
1389 max = read(fd, tmp_buf, MIN((size_t) buf_length, sizeof(tmp_buf)));
1391 if ((max > 0) && !utf8)
1393 /* Use dir_buf as a temporary buffer. Note that dir_buf must
1394 * be as large as tmp_buf.
1396 max = convert_m3u(tmp_buf, max, sizeof(tmp_buf), dir_buf);
1401 mutex_unlock(&playlist->control_mutex);
1403 if (max < 0)
1405 if (control_file)
1406 splash(HZ*2, ID2P(LANG_PLAYLIST_CONTROL_ACCESS_ERROR));
1407 else
1408 splash(HZ*2, ID2P(LANG_PLAYLIST_ACCESS_ERROR));
1410 return max;
1414 strlcpy(dir_buf, playlist->filename, playlist->dirlen);
1416 return (format_track_path(buf, tmp_buf, buf_length, max, dir_buf));
1419 static int get_next_directory(char *dir){
1420 return get_next_dir(dir,true,false);
1423 static int get_previous_directory(char *dir){
1424 return get_next_dir(dir,false,false);
1428 * search through all the directories (starting with the current) to find
1429 * one that has tracks to play
1431 static int get_next_dir(char *dir, bool is_forward, bool recursion)
1433 struct playlist_info* playlist = &current_playlist;
1434 int result = -1;
1435 char *start_dir = NULL;
1436 bool exit = false;
1437 int i;
1438 struct tree_context* tc = tree_get_context();
1439 int saved_dirfilter = *(tc->dirfilter);
1441 /* process random folder advance */
1442 if (global_settings.next_folder == FOLDER_ADVANCE_RANDOM)
1444 int fd = open(ROCKBOX_DIR "/folder_advance_list.dat", O_RDONLY);
1445 if (fd >= 0)
1447 char buffer[MAX_PATH];
1448 int folder_count = 0;
1449 srand(current_tick);
1450 *(tc->dirfilter) = SHOW_MUSIC;
1451 tc->sort_dir = global_settings.sort_dir;
1452 read(fd,&folder_count,sizeof(int));
1453 if (!folder_count)
1454 exit = true;
1455 while (!exit)
1457 i = rand()%folder_count;
1458 lseek(fd,sizeof(int) + (MAX_PATH*i),SEEK_SET);
1459 read(fd,buffer,MAX_PATH);
1460 if (check_subdir_for_music(buffer, "", false) ==0)
1461 exit = true;
1463 if (folder_count)
1464 strcpy(dir,buffer);
1465 close(fd);
1466 *(tc->dirfilter) = saved_dirfilter;
1467 tc->sort_dir = global_settings.sort_dir;
1468 reload_directory();
1469 return 0;
1473 /* not random folder advance (or random folder advance unavailable) */
1474 if (recursion)
1476 /* start with root */
1477 dir[0] = '\0';
1479 else
1481 /* start with current directory */
1482 strlcpy(dir, playlist->filename, playlist->dirlen);
1485 /* use the tree browser dircache to load files */
1486 *(tc->dirfilter) = SHOW_ALL;
1488 /* set up sorting/direction */
1489 tc->sort_dir = global_settings.sort_dir;
1490 if (!is_forward)
1492 static const char sortpairs[] =
1494 [SORT_ALPHA] = SORT_ALPHA_REVERSED,
1495 [SORT_DATE] = SORT_DATE_REVERSED,
1496 [SORT_TYPE] = SORT_TYPE_REVERSED,
1497 [SORT_ALPHA_REVERSED] = SORT_ALPHA,
1498 [SORT_DATE_REVERSED] = SORT_DATE,
1499 [SORT_TYPE_REVERSED] = SORT_TYPE,
1502 if ((unsigned)tc->sort_dir < sizeof(sortpairs))
1503 tc->sort_dir = sortpairs[tc->sort_dir];
1506 while (!exit)
1508 struct entry *files;
1509 int num_files = 0;
1510 int i;
1512 if (ft_load(tc, (dir[0]=='\0')?"/":dir) < 0)
1514 exit = true;
1515 result = -1;
1516 break;
1519 files = (struct entry*) tc->dircache;
1520 num_files = tc->filesindir;
1522 for (i=0; i<num_files; i++)
1524 /* user abort */
1525 if (action_userabort(TIMEOUT_NOBLOCK))
1527 result = -1;
1528 exit = true;
1529 break;
1532 if (files[i].attr & ATTR_DIRECTORY)
1534 if (!start_dir)
1536 result = check_subdir_for_music(dir, files[i].name, true);
1537 if (result != -1)
1539 exit = true;
1540 break;
1543 else if (!strcmp(start_dir, files[i].name))
1544 start_dir = NULL;
1548 if (!exit)
1550 /* move down to parent directory. current directory name is
1551 stored as the starting point for the search in parent */
1552 start_dir = strrchr(dir, '/');
1553 if (start_dir)
1555 *start_dir = '\0';
1556 start_dir++;
1558 else
1559 break;
1563 /* restore dirfilter */
1564 *(tc->dirfilter) = saved_dirfilter;
1565 tc->sort_dir = global_settings.sort_dir;
1567 /* special case if nothing found: try start searching again from root */
1568 if (result == -1 && !recursion){
1569 result = get_next_dir(dir, is_forward, true);
1572 return result;
1576 * Checks if there are any music files in the dir or any of its
1577 * subdirectories. May be called recursively.
1579 static int check_subdir_for_music(char *dir, const char *subdir, bool recurse)
1581 int result = -1;
1582 int dirlen = strlen(dir);
1583 int num_files = 0;
1584 int i;
1585 struct entry *files;
1586 bool has_music = false;
1587 bool has_subdir = false;
1588 struct tree_context* tc = tree_get_context();
1590 snprintf(dir+dirlen, MAX_PATH-dirlen, "/%s", subdir);
1592 if (ft_load(tc, dir) < 0)
1594 return -2;
1597 files = (struct entry*) tc->dircache;
1598 num_files = tc->filesindir;
1600 for (i=0; i<num_files; i++)
1602 if (files[i].attr & ATTR_DIRECTORY)
1603 has_subdir = true;
1604 else if ((files[i].attr & FILE_ATTR_MASK) == FILE_ATTR_AUDIO)
1606 has_music = true;
1607 break;
1611 if (has_music)
1612 return 0;
1614 if (has_subdir && recurse)
1616 for (i=0; i<num_files; i++)
1618 if (action_userabort(TIMEOUT_NOBLOCK))
1620 result = -2;
1621 break;
1624 if (files[i].attr & ATTR_DIRECTORY)
1626 result = check_subdir_for_music(dir, files[i].name, true);
1627 if (!result)
1628 break;
1633 if (result < 0)
1635 if (dirlen)
1637 dir[dirlen] = '\0';
1639 else
1641 strcpy(dir, "/");
1644 /* we now need to reload our current directory */
1645 if(ft_load(tc, dir) < 0)
1646 splash(HZ*2, ID2P(LANG_PLAYLIST_DIRECTORY_ACCESS_ERROR));
1648 return result;
1652 * Returns absolute path of track
1654 static int format_track_path(char *dest, char *src, int buf_length, int max,
1655 const char *dir)
1657 int i = 0;
1658 int j;
1659 char *temp_ptr;
1661 /* Zero-terminate the file name */
1662 while((src[i] != '\n') &&
1663 (src[i] != '\r') &&
1664 (i < max))
1665 i++;
1667 /* Now work back killing white space */
1668 while((src[i-1] == ' ') ||
1669 (src[i-1] == '\t'))
1670 i--;
1672 src[i]=0;
1674 /* replace backslashes with forward slashes */
1675 for ( j=0; j<i; j++ )
1676 if ( src[j] == '\\' )
1677 src[j] = '/';
1679 if('/' == src[0])
1681 strlcpy(dest, src, buf_length);
1683 else
1685 /* handle dos style drive letter */
1686 if (':' == src[1])
1687 strlcpy(dest, &src[2], buf_length);
1688 else if (!strncmp(src, "../", 3))
1690 /* handle relative paths */
1691 i=3;
1692 while(!strncmp(&src[i], "../", 3))
1693 i += 3;
1694 for (j=0; j<i/3; j++) {
1695 temp_ptr = strrchr(dir, '/');
1696 if (temp_ptr)
1697 *temp_ptr = '\0';
1698 else
1699 break;
1701 snprintf(dest, buf_length, "%s/%s", dir, &src[i]);
1703 else if ( '.' == src[0] && '/' == src[1] ) {
1704 snprintf(dest, buf_length, "%s/%s", dir, &src[2]);
1706 else {
1707 snprintf(dest, buf_length, "%s/%s", dir, src);
1711 return 0;
1715 * Display splash message showing progress of playlist/directory insertion or
1716 * save.
1718 static void display_playlist_count(int count, const unsigned char *fmt,
1719 bool final)
1721 static long talked_tick = 0;
1722 long id = P2ID(fmt);
1723 if(global_settings.talk_menu && id>=0)
1725 if(final || (count && (talked_tick == 0
1726 || TIME_AFTER(current_tick, talked_tick+5*HZ))))
1728 talked_tick = current_tick;
1729 talk_number(count, false);
1730 talk_id(id, true);
1733 fmt = P2STR(fmt);
1735 splashf(0, fmt, count, str(LANG_OFF_ABORT));
1739 * Display buffer full message
1741 static void display_buffer_full(void)
1743 splash(HZ*2, ID2P(LANG_PLAYLIST_BUFFER_FULL));
1747 * Flush any cached control commands to disk. Called when playlist is being
1748 * modified. Returns 0 on success and -1 on failure.
1750 static int flush_cached_control(struct playlist_info* playlist)
1752 int result = 0;
1753 int i;
1755 if (!playlist->num_cached)
1756 return 0;
1758 lseek(playlist->control_fd, 0, SEEK_END);
1760 for (i=0; i<playlist->num_cached; i++)
1762 struct playlist_control_cache* cache =
1763 &(playlist->control_cache[i]);
1765 switch (cache->command)
1767 case PLAYLIST_COMMAND_PLAYLIST:
1768 result = fdprintf(playlist->control_fd, "P:%d:%s:%s\n",
1769 cache->i1, cache->s1, cache->s2);
1770 break;
1771 case PLAYLIST_COMMAND_ADD:
1772 case PLAYLIST_COMMAND_QUEUE:
1773 result = fdprintf(playlist->control_fd, "%c:%d:%d:",
1774 (cache->command == PLAYLIST_COMMAND_ADD)?'A':'Q',
1775 cache->i1, cache->i2);
1776 if (result > 0)
1778 /* save the position in file where name is written */
1779 int* seek_pos = (int *)cache->data;
1780 *seek_pos = lseek(playlist->control_fd, 0, SEEK_CUR);
1781 result = fdprintf(playlist->control_fd, "%s\n",
1782 cache->s1);
1784 break;
1785 case PLAYLIST_COMMAND_DELETE:
1786 result = fdprintf(playlist->control_fd, "D:%d\n", cache->i1);
1787 break;
1788 case PLAYLIST_COMMAND_SHUFFLE:
1789 result = fdprintf(playlist->control_fd, "S:%d:%d\n",
1790 cache->i1, cache->i2);
1791 break;
1792 case PLAYLIST_COMMAND_UNSHUFFLE:
1793 result = fdprintf(playlist->control_fd, "U:%d\n", cache->i1);
1794 break;
1795 case PLAYLIST_COMMAND_RESET:
1796 result = fdprintf(playlist->control_fd, "R\n");
1797 break;
1798 default:
1799 break;
1802 if (result <= 0)
1803 break;
1806 if (result > 0)
1808 playlist->num_cached = 0;
1809 playlist->pending_control_sync = true;
1811 result = 0;
1813 else
1815 result = -1;
1816 splash(HZ*2, ID2P(LANG_PLAYLIST_CONTROL_UPDATE_ERROR));
1819 return result;
1823 * Update control data with new command. Depending on the command, it may be
1824 * cached or flushed to disk.
1826 static int update_control(struct playlist_info* playlist,
1827 enum playlist_command command, int i1, int i2,
1828 const char* s1, const char* s2, void* data)
1830 int result = 0;
1831 struct playlist_control_cache* cache;
1832 bool flush = false;
1834 mutex_lock(&playlist->control_mutex);
1836 cache = &(playlist->control_cache[playlist->num_cached++]);
1838 cache->command = command;
1839 cache->i1 = i1;
1840 cache->i2 = i2;
1841 cache->s1 = s1;
1842 cache->s2 = s2;
1843 cache->data = data;
1845 switch (command)
1847 case PLAYLIST_COMMAND_PLAYLIST:
1848 case PLAYLIST_COMMAND_ADD:
1849 case PLAYLIST_COMMAND_QUEUE:
1850 #ifndef HAVE_DIRCACHE
1851 case PLAYLIST_COMMAND_DELETE:
1852 case PLAYLIST_COMMAND_RESET:
1853 #endif
1854 flush = true;
1855 break;
1856 case PLAYLIST_COMMAND_SHUFFLE:
1857 case PLAYLIST_COMMAND_UNSHUFFLE:
1858 default:
1859 /* only flush when needed */
1860 break;
1863 if (flush || playlist->num_cached == PLAYLIST_MAX_CACHE)
1864 result = flush_cached_control(playlist);
1866 mutex_unlock(&playlist->control_mutex);
1868 return result;
1872 * sync control file to disk
1874 static void sync_control(struct playlist_info* playlist, bool force)
1876 #ifdef HAVE_DIRCACHE
1877 if (playlist->started && force)
1878 #else
1879 (void) force;
1881 if (playlist->started)
1882 #endif
1884 if (playlist->pending_control_sync)
1886 mutex_lock(&playlist->control_mutex);
1887 fsync(playlist->control_fd);
1888 playlist->pending_control_sync = false;
1889 mutex_unlock(&playlist->control_mutex);
1895 * Rotate indices such that first_index is index 0
1897 static int rotate_index(const struct playlist_info* playlist, int index)
1899 index -= playlist->first_index;
1900 if (index < 0)
1901 index += playlist->amount;
1903 return index;
1907 * Initialize playlist entries at startup
1909 void playlist_init(void)
1911 struct playlist_info* playlist = &current_playlist;
1913 playlist->current = true;
1914 strlcpy(playlist->control_filename, PLAYLIST_CONTROL_FILE,
1915 sizeof(playlist->control_filename));
1916 playlist->fd = -1;
1917 playlist->control_fd = -1;
1918 playlist->max_playlist_size = global_settings.max_files_in_playlist;
1919 playlist->indices = buffer_alloc(
1920 playlist->max_playlist_size * sizeof(int));
1921 playlist->buffer_size =
1922 AVERAGE_FILENAME_LENGTH * global_settings.max_files_in_dir;
1923 playlist->buffer = buffer_alloc(playlist->buffer_size);
1924 mutex_init(&playlist->control_mutex);
1925 empty_playlist(playlist, true);
1927 #ifdef HAVE_DIRCACHE
1928 playlist->filenames = buffer_alloc(
1929 playlist->max_playlist_size * sizeof(int));
1930 memset(playlist->filenames, 0,
1931 playlist->max_playlist_size * sizeof(int));
1932 create_thread(playlist_thread, playlist_stack, sizeof(playlist_stack),
1933 0, playlist_thread_name IF_PRIO(, PRIORITY_BACKGROUND)
1934 IF_COP(, CPU));
1935 queue_init(&playlist_queue, true);
1936 #endif
1940 * Clean playlist at shutdown
1942 void playlist_shutdown(void)
1944 struct playlist_info* playlist = &current_playlist;
1946 if (playlist->control_fd >= 0)
1948 mutex_lock(&playlist->control_mutex);
1950 if (playlist->num_cached > 0)
1951 flush_cached_control(playlist);
1953 close(playlist->control_fd);
1955 mutex_unlock(&playlist->control_mutex);
1960 * Create new playlist
1962 int playlist_create(const char *dir, const char *file)
1964 struct playlist_info* playlist = &current_playlist;
1966 new_playlist(playlist, dir, file);
1968 if (file)
1969 /* load the playlist file */
1970 add_indices_to_playlist(playlist, NULL, 0);
1972 return 0;
1975 #define PLAYLIST_COMMAND_SIZE (MAX_PATH+12)
1978 * Restore the playlist state based on control file commands. Called to
1979 * resume playback after shutdown.
1981 int playlist_resume(void)
1983 struct playlist_info* playlist = &current_playlist;
1984 char *buffer;
1985 size_t buflen;
1986 int nread;
1987 int total_read = 0;
1988 int control_file_size = 0;
1989 bool first = true;
1990 bool sorted = true;
1992 /* use mp3 buffer for maximum load speed */
1993 #if CONFIG_CODEC != SWCODEC
1994 talk_buffer_steal(); /* we use the mp3 buffer, need to tell */
1995 buflen = (audiobufend - audiobuf);
1996 buffer = (char *)audiobuf;
1997 #else
1998 buffer = (char *)audio_get_buffer(false, &buflen);
1999 #endif
2001 empty_playlist(playlist, true);
2003 splash(0, ID2P(LANG_WAIT));
2004 playlist->control_fd = open(playlist->control_filename, O_RDWR);
2005 if (playlist->control_fd < 0)
2007 splash(HZ*2, ID2P(LANG_PLAYLIST_CONTROL_ACCESS_ERROR));
2008 return -1;
2010 playlist->control_created = true;
2012 control_file_size = filesize(playlist->control_fd);
2013 if (control_file_size <= 0)
2015 splash(HZ*2, ID2P(LANG_PLAYLIST_CONTROL_ACCESS_ERROR));
2016 return -1;
2019 /* read a small amount first to get the header */
2020 nread = read(playlist->control_fd, buffer,
2021 PLAYLIST_COMMAND_SIZE<buflen?PLAYLIST_COMMAND_SIZE:buflen);
2022 if(nread <= 0)
2024 splash(HZ*2, ID2P(LANG_PLAYLIST_CONTROL_ACCESS_ERROR));
2025 return -1;
2028 playlist->started = true;
2030 while (1)
2032 int result = 0;
2033 int count;
2034 enum playlist_command current_command = PLAYLIST_COMMAND_COMMENT;
2035 int last_newline = 0;
2036 int str_count = -1;
2037 bool newline = true;
2038 bool exit_loop = false;
2039 char *p = buffer;
2040 char *str1 = NULL;
2041 char *str2 = NULL;
2042 char *str3 = NULL;
2043 unsigned long last_tick = current_tick;
2044 bool useraborted = false;
2046 for(count=0; count<nread && !exit_loop && !useraborted; count++,p++)
2048 /* So a splash while we are loading. */
2049 if (TIME_AFTER(current_tick, last_tick + HZ/4))
2051 splashf(0, str(LANG_LOADING_PERCENT),
2052 (total_read+count)*100/control_file_size,
2053 str(LANG_OFF_ABORT));
2054 if (action_userabort(TIMEOUT_NOBLOCK))
2056 useraborted = true;
2057 break;
2059 last_tick = current_tick;
2062 /* Are we on a new line? */
2063 if((*p == '\n') || (*p == '\r'))
2065 *p = '\0';
2067 /* save last_newline in case we need to load more data */
2068 last_newline = count;
2070 switch (current_command)
2072 case PLAYLIST_COMMAND_PLAYLIST:
2074 /* str1=version str2=dir str3=file */
2075 int version;
2077 if (!str1)
2079 result = -1;
2080 exit_loop = true;
2081 break;
2084 if (!str2)
2085 str2 = "";
2087 if (!str3)
2088 str3 = "";
2090 version = atoi(str1);
2092 if (version != PLAYLIST_CONTROL_FILE_VERSION)
2093 return -1;
2095 update_playlist_filename(playlist, str2, str3);
2097 if (str3[0] != '\0')
2099 /* NOTE: add_indices_to_playlist() overwrites the
2100 audiobuf so we need to reload control file
2101 data */
2102 add_indices_to_playlist(playlist, NULL, 0);
2104 else if (str2[0] != '\0')
2106 playlist->in_ram = true;
2107 resume_directory(str2);
2110 /* load the rest of the data */
2111 first = false;
2112 exit_loop = true;
2114 break;
2116 case PLAYLIST_COMMAND_ADD:
2117 case PLAYLIST_COMMAND_QUEUE:
2119 /* str1=position str2=last_position str3=file */
2120 int position, last_position;
2121 bool queue;
2123 if (!str1 || !str2 || !str3)
2125 result = -1;
2126 exit_loop = true;
2127 break;
2130 position = atoi(str1);
2131 last_position = atoi(str2);
2133 queue = (current_command == PLAYLIST_COMMAND_ADD)?
2134 false:true;
2136 /* seek position is based on str3's position in
2137 buffer */
2138 if (add_track_to_playlist(playlist, str3, position,
2139 queue, total_read+(str3-buffer)) < 0)
2140 return -1;
2142 playlist->last_insert_pos = last_position;
2144 break;
2146 case PLAYLIST_COMMAND_DELETE:
2148 /* str1=position */
2149 int position;
2151 if (!str1)
2153 result = -1;
2154 exit_loop = true;
2155 break;
2158 position = atoi(str1);
2160 if (remove_track_from_playlist(playlist, position,
2161 false) < 0)
2162 return -1;
2164 break;
2166 case PLAYLIST_COMMAND_SHUFFLE:
2168 /* str1=seed str2=first_index */
2169 int seed;
2171 if (!str1 || !str2)
2173 result = -1;
2174 exit_loop = true;
2175 break;
2178 if (!sorted)
2180 /* Always sort list before shuffling */
2181 sort_playlist(playlist, false, false);
2184 seed = atoi(str1);
2185 playlist->first_index = atoi(str2);
2187 if (randomise_playlist(playlist, seed, false,
2188 false) < 0)
2189 return -1;
2190 sorted = false;
2191 break;
2193 case PLAYLIST_COMMAND_UNSHUFFLE:
2195 /* str1=first_index */
2196 if (!str1)
2198 result = -1;
2199 exit_loop = true;
2200 break;
2203 playlist->first_index = atoi(str1);
2205 if (sort_playlist(playlist, false, false) < 0)
2206 return -1;
2208 sorted = true;
2209 break;
2211 case PLAYLIST_COMMAND_RESET:
2213 playlist->last_insert_pos = -1;
2214 break;
2216 case PLAYLIST_COMMAND_COMMENT:
2217 default:
2218 break;
2221 newline = true;
2223 /* to ignore any extra newlines */
2224 current_command = PLAYLIST_COMMAND_COMMENT;
2226 else if(newline)
2228 newline = false;
2230 /* first non-comment line must always specify playlist */
2231 if (first && *p != 'P' && *p != '#')
2233 result = -1;
2234 exit_loop = true;
2235 break;
2238 switch (*p)
2240 case 'P':
2241 /* playlist can only be specified once */
2242 if (!first)
2244 result = -1;
2245 exit_loop = true;
2246 break;
2249 current_command = PLAYLIST_COMMAND_PLAYLIST;
2250 break;
2251 case 'A':
2252 current_command = PLAYLIST_COMMAND_ADD;
2253 break;
2254 case 'Q':
2255 current_command = PLAYLIST_COMMAND_QUEUE;
2256 break;
2257 case 'D':
2258 current_command = PLAYLIST_COMMAND_DELETE;
2259 break;
2260 case 'S':
2261 current_command = PLAYLIST_COMMAND_SHUFFLE;
2262 break;
2263 case 'U':
2264 current_command = PLAYLIST_COMMAND_UNSHUFFLE;
2265 break;
2266 case 'R':
2267 current_command = PLAYLIST_COMMAND_RESET;
2268 break;
2269 case '#':
2270 current_command = PLAYLIST_COMMAND_COMMENT;
2271 break;
2272 default:
2273 result = -1;
2274 exit_loop = true;
2275 break;
2278 str_count = -1;
2279 str1 = NULL;
2280 str2 = NULL;
2281 str3 = NULL;
2283 else if(current_command != PLAYLIST_COMMAND_COMMENT)
2285 /* all control file strings are separated with a colon.
2286 Replace the colon with 0 to get proper strings that can be
2287 used by commands above */
2288 if (*p == ':')
2290 *p = '\0';
2291 str_count++;
2293 if ((count+1) < nread)
2295 switch (str_count)
2297 case 0:
2298 str1 = p+1;
2299 break;
2300 case 1:
2301 str2 = p+1;
2302 break;
2303 case 2:
2304 str3 = p+1;
2305 break;
2306 default:
2307 /* allow last string to contain colons */
2308 *p = ':';
2309 break;
2316 if (result < 0)
2318 splash(HZ*2, ID2P(LANG_PLAYLIST_CONTROL_INVALID));
2319 return result;
2322 if (useraborted)
2324 splash(HZ*2, ID2P(LANG_CANCEL));
2325 return -1;
2327 if (!newline || (exit_loop && count<nread))
2329 if ((total_read + count) >= control_file_size)
2331 /* no newline at end of control file */
2332 splash(HZ*2, ID2P(LANG_PLAYLIST_CONTROL_INVALID));
2333 return -1;
2336 /* We didn't end on a newline or we exited loop prematurely.
2337 Either way, re-read the remainder. */
2338 count = last_newline;
2339 lseek(playlist->control_fd, total_read+count, SEEK_SET);
2342 total_read += count;
2344 if (first)
2345 /* still looking for header */
2346 nread = read(playlist->control_fd, buffer,
2347 PLAYLIST_COMMAND_SIZE<buflen?PLAYLIST_COMMAND_SIZE:buflen);
2348 else
2349 nread = read(playlist->control_fd, buffer, buflen);
2351 /* Terminate on EOF */
2352 if(nread <= 0)
2354 break;
2358 #ifdef HAVE_DIRCACHE
2359 queue_post(&playlist_queue, PLAYLIST_LOAD_POINTERS, 0);
2360 #endif
2362 return 0;
2366 * Add track to in_ram playlist. Used when playing directories.
2368 int playlist_add(const char *filename)
2370 struct playlist_info* playlist = &current_playlist;
2371 int len = strlen(filename);
2373 if((len+1 > playlist->buffer_size - playlist->buffer_end_pos) ||
2374 (playlist->amount >= playlist->max_playlist_size))
2376 display_buffer_full();
2377 return -1;
2380 playlist->indices[playlist->amount] = playlist->buffer_end_pos;
2381 #ifdef HAVE_DIRCACHE
2382 playlist->filenames[playlist->amount] = NULL;
2383 #endif
2384 playlist->amount++;
2386 strcpy(&playlist->buffer[playlist->buffer_end_pos], filename);
2387 playlist->buffer_end_pos += len;
2388 playlist->buffer[playlist->buffer_end_pos++] = '\0';
2390 return 0;
2393 /* shuffle newly created playlist using random seed. */
2394 int playlist_shuffle(int random_seed, int start_index)
2396 struct playlist_info* playlist = &current_playlist;
2398 unsigned int seek_pos = 0;
2399 bool start_current = false;
2401 if (start_index >= 0 && global_settings.play_selected)
2403 /* store the seek position before the shuffle */
2404 seek_pos = playlist->indices[start_index];
2405 playlist->index = playlist->first_index = start_index;
2406 start_current = true;
2409 randomise_playlist(playlist, random_seed, start_current, true);
2411 return playlist->index;
2414 /* start playing current playlist at specified index/offset */
2415 void playlist_start(int start_index, int offset)
2417 struct playlist_info* playlist = &current_playlist;
2419 /* Cancel FM radio selection as previous music. For cases where we start
2420 playback without going to the WPS, such as playlist insert.. or
2421 playlist catalog. */
2422 previous_music_is_wps();
2424 playlist->index = start_index;
2426 #if CONFIG_CODEC != SWCODEC
2427 talk_buffer_steal(); /* will use the mp3 buffer */
2428 #endif
2430 playlist->started = true;
2431 sync_control(playlist, false);
2432 audio_play(offset);
2435 /* Returns false if 'steps' is out of bounds, else true */
2436 bool playlist_check(int steps)
2438 struct playlist_info* playlist = &current_playlist;
2440 /* always allow folder navigation */
2441 if (global_settings.next_folder && playlist->in_ram)
2442 return true;
2444 int index = get_next_index(playlist, steps, -1);
2446 if (index < 0 && steps >= 0 && global_settings.repeat_mode == REPEAT_SHUFFLE)
2447 index = get_next_index(playlist, steps, REPEAT_ALL);
2449 return (index >= 0);
2452 /* get trackname of track that is "steps" away from current playing track.
2453 NULL is used to identify end of playlist */
2454 char* playlist_peek(int steps)
2456 struct playlist_info* playlist = &current_playlist;
2457 int seek;
2458 char *temp_ptr;
2459 int index;
2460 bool control_file;
2462 index = get_next_index(playlist, steps, -1);
2463 if (index < 0)
2464 return NULL;
2466 control_file = playlist->indices[index] & PLAYLIST_INSERT_TYPE_MASK;
2467 seek = playlist->indices[index] & PLAYLIST_SEEK_MASK;
2469 if (get_filename(playlist, index, seek, control_file, now_playing,
2470 MAX_PATH+1) < 0)
2471 return NULL;
2473 temp_ptr = now_playing;
2475 if (!playlist->in_ram || control_file)
2477 /* remove bogus dirs from beginning of path
2478 (workaround for buggy playlist creation tools) */
2479 while (temp_ptr)
2481 if (file_exists(temp_ptr))
2482 break;
2484 temp_ptr = strchr(temp_ptr+1, '/');
2487 if (!temp_ptr)
2489 /* Even though this is an invalid file, we still need to pass a
2490 file name to the caller because NULL is used to indicate end
2491 of playlist */
2492 return now_playing;
2496 return temp_ptr;
2500 * Update indices as track has changed
2502 int playlist_next(int steps)
2504 struct playlist_info* playlist = &current_playlist;
2505 int index;
2507 if ( (steps > 0)
2508 #ifdef AB_REPEAT_ENABLE
2509 && (global_settings.repeat_mode != REPEAT_AB)
2510 #endif
2511 && (global_settings.repeat_mode != REPEAT_ONE) )
2513 int i, j;
2515 /* We need to delete all the queued songs */
2516 for (i=0, j=steps; i<j; i++)
2518 index = get_next_index(playlist, i, -1);
2520 if (playlist->indices[index] & PLAYLIST_QUEUE_MASK)
2522 remove_track_from_playlist(playlist, index, true);
2523 steps--; /* one less track */
2528 index = get_next_index(playlist, steps, -1);
2530 if (index < 0)
2532 /* end of playlist... or is it */
2533 if (global_settings.repeat_mode == REPEAT_SHUFFLE &&
2534 playlist->amount > 1)
2536 /* Repeat shuffle mode. Re-shuffle playlist and resume play */
2537 playlist->first_index = 0;
2538 sort_playlist(playlist, false, false);
2539 randomise_playlist(playlist, current_tick, false, true);
2540 #if CONFIG_CODEC != SWCODEC
2541 playlist_start(0, 0);
2542 #endif
2543 playlist->index = 0;
2544 index = 0;
2546 else if (playlist->in_ram && global_settings.next_folder)
2548 index = create_and_play_dir(steps, true);
2550 if (index >= 0)
2552 playlist->index = index;
2556 return index;
2559 playlist->index = index;
2561 if (playlist->last_insert_pos >= 0 && steps > 0)
2563 /* check to see if we've gone beyond the last inserted track */
2564 int cur = rotate_index(playlist, index);
2565 int last_pos = rotate_index(playlist, playlist->last_insert_pos);
2567 if (cur > last_pos)
2569 /* reset last inserted track */
2570 playlist->last_insert_pos = -1;
2572 if (playlist->control_fd >= 0)
2574 int result = update_control(playlist, PLAYLIST_COMMAND_RESET,
2575 -1, -1, NULL, NULL, NULL);
2577 if (result < 0)
2578 return result;
2580 sync_control(playlist, false);
2585 return index;
2588 /* try playing next or previous folder */
2589 bool playlist_next_dir(int direction)
2591 /* not to mess up real playlists */
2592 if(!current_playlist.in_ram)
2593 return false;
2595 return create_and_play_dir(direction, false) >= 0;
2598 /* Get resume info for current playing song. If return value is -1 then
2599 settings shouldn't be saved. */
2600 int playlist_get_resume_info(int *resume_index)
2602 struct playlist_info* playlist = &current_playlist;
2604 *resume_index = playlist->index;
2606 return 0;
2609 /* Update resume info for current playing song. Returns -1 on error. */
2610 int playlist_update_resume_info(const struct mp3entry* id3)
2612 struct playlist_info* playlist = &current_playlist;
2614 if (id3)
2616 if (global_status.resume_index != playlist->index ||
2617 global_status.resume_offset != id3->offset)
2619 global_status.resume_index = playlist->index;
2620 global_status.resume_offset = id3->offset;
2621 status_save();
2624 else
2626 global_status.resume_index = -1;
2627 global_status.resume_offset = -1;
2628 status_save();
2631 return 0;
2634 /* Returns index of current playing track for display purposes. This value
2635 should not be used for resume purposes as it doesn't represent the actual
2636 index into the playlist */
2637 int playlist_get_display_index(void)
2639 struct playlist_info* playlist = &current_playlist;
2641 /* first_index should always be index 0 for display purposes */
2642 int index = rotate_index(playlist, playlist->index);
2644 return (index+1);
2647 /* returns number of tracks in current playlist */
2648 int playlist_amount(void)
2650 return playlist_amount_ex(NULL);
2652 /* set playlist->last_shuffle_start to playlist->amount for
2653 PLAYLIST_INSERT_LAST_SHUFFLED command purposes*/
2654 void playlist_set_last_shuffled_start(void)
2656 struct playlist_info* playlist = &current_playlist;
2657 playlist->last_shuffled_start = playlist->amount;
2660 * Create a new playlist If playlist is not NULL then we're loading a
2661 * playlist off disk for viewing/editing. The index_buffer is used to store
2662 * playlist indices (required for and only used if !current playlist). The
2663 * temp_buffer (if not NULL) is used as a scratchpad when loading indices.
2665 int playlist_create_ex(struct playlist_info* playlist,
2666 const char* dir, const char* file,
2667 void* index_buffer, int index_buffer_size,
2668 void* temp_buffer, int temp_buffer_size)
2670 if (!playlist)
2671 playlist = &current_playlist;
2672 else
2674 /* Initialize playlist structure */
2675 int r = rand() % 10;
2676 playlist->current = false;
2678 /* Use random name for control file */
2679 snprintf(playlist->control_filename, sizeof(playlist->control_filename),
2680 "%s.%d", PLAYLIST_CONTROL_FILE, r);
2681 playlist->fd = -1;
2682 playlist->control_fd = -1;
2684 if (index_buffer)
2686 int num_indices = index_buffer_size / sizeof(int);
2688 #ifdef HAVE_DIRCACHE
2689 num_indices /= 2;
2690 #endif
2691 if (num_indices > global_settings.max_files_in_playlist)
2692 num_indices = global_settings.max_files_in_playlist;
2694 playlist->max_playlist_size = num_indices;
2695 playlist->indices = index_buffer;
2696 #ifdef HAVE_DIRCACHE
2697 playlist->filenames = (const struct dircache_entry **)
2698 &playlist->indices[num_indices];
2699 #endif
2701 else
2703 playlist->max_playlist_size = current_playlist.max_playlist_size;
2704 playlist->indices = current_playlist.indices;
2705 #ifdef HAVE_DIRCACHE
2706 playlist->filenames = current_playlist.filenames;
2707 #endif
2710 playlist->buffer_size = 0;
2711 playlist->buffer = NULL;
2712 mutex_init(&playlist->control_mutex);
2715 new_playlist(playlist, dir, file);
2717 if (file)
2718 /* load the playlist file */
2719 add_indices_to_playlist(playlist, temp_buffer, temp_buffer_size);
2721 return 0;
2725 * Set the specified playlist as the current.
2726 * NOTE: You will get undefined behaviour if something is already playing so
2727 * remember to stop before calling this. Also, this call will
2728 * effectively close your playlist, making it unusable.
2730 int playlist_set_current(struct playlist_info* playlist)
2732 if (!playlist || (check_control(playlist) < 0))
2733 return -1;
2735 empty_playlist(&current_playlist, false);
2737 strlcpy(current_playlist.filename, playlist->filename,
2738 sizeof(current_playlist.filename));
2740 current_playlist.utf8 = playlist->utf8;
2741 current_playlist.fd = playlist->fd;
2743 close(playlist->control_fd);
2744 close(current_playlist.control_fd);
2745 remove(current_playlist.control_filename);
2746 if (rename(playlist->control_filename,
2747 current_playlist.control_filename) < 0)
2748 return -1;
2749 current_playlist.control_fd = open(current_playlist.control_filename,
2750 O_RDWR);
2751 if (current_playlist.control_fd < 0)
2752 return -1;
2753 current_playlist.control_created = true;
2755 current_playlist.dirlen = playlist->dirlen;
2757 if (playlist->indices && playlist->indices != current_playlist.indices)
2759 memcpy(current_playlist.indices, playlist->indices,
2760 playlist->max_playlist_size*sizeof(int));
2761 #ifdef HAVE_DIRCACHE
2762 memcpy(current_playlist.filenames, playlist->filenames,
2763 playlist->max_playlist_size*sizeof(int));
2764 #endif
2767 current_playlist.first_index = playlist->first_index;
2768 current_playlist.amount = playlist->amount;
2769 current_playlist.last_insert_pos = playlist->last_insert_pos;
2770 current_playlist.seed = playlist->seed;
2771 current_playlist.shuffle_modified = playlist->shuffle_modified;
2772 current_playlist.deleted = playlist->deleted;
2773 current_playlist.num_inserted_tracks = playlist->num_inserted_tracks;
2775 memcpy(current_playlist.control_cache, playlist->control_cache,
2776 sizeof(current_playlist.control_cache));
2777 current_playlist.num_cached = playlist->num_cached;
2778 current_playlist.pending_control_sync = playlist->pending_control_sync;
2780 return 0;
2782 struct playlist_info *playlist_get_current(void)
2784 return &current_playlist;
2787 * Close files and delete control file for non-current playlist.
2789 void playlist_close(struct playlist_info* playlist)
2791 if (!playlist)
2792 return;
2794 if (playlist->fd >= 0)
2795 close(playlist->fd);
2797 if (playlist->control_fd >= 0)
2798 close(playlist->control_fd);
2800 if (playlist->control_created)
2801 remove(playlist->control_filename);
2804 void playlist_sync(struct playlist_info* playlist)
2806 if (!playlist)
2807 playlist = &current_playlist;
2809 sync_control(playlist, false);
2810 if ((audio_status() & AUDIO_STATUS_PLAY) && playlist->started)
2811 audio_flush_and_reload_tracks();
2813 #ifdef HAVE_DIRCACHE
2814 queue_post(&playlist_queue, PLAYLIST_LOAD_POINTERS, 0);
2815 #endif
2819 * Insert track into playlist at specified position (or one of the special
2820 * positions). Returns position where track was inserted or -1 if error.
2822 int playlist_insert_track(struct playlist_info* playlist, const char *filename,
2823 int position, bool queue, bool sync)
2825 int result;
2827 if (!playlist)
2828 playlist = &current_playlist;
2830 if (check_control(playlist) < 0)
2832 splash(HZ*2, ID2P(LANG_PLAYLIST_CONTROL_ACCESS_ERROR));
2833 return -1;
2836 result = add_track_to_playlist(playlist, filename, position, queue, -1);
2838 /* Check if we want manually sync later. For example when adding
2839 * bunch of files from tagcache, syncing after every file wouldn't be
2840 * a good thing to do. */
2841 if (sync && result >= 0)
2842 playlist_sync(playlist);
2844 return result;
2848 * Insert all tracks from specified directory into playlist.
2850 int playlist_insert_directory(struct playlist_info* playlist,
2851 const char *dirname, int position, bool queue,
2852 bool recurse)
2854 int result;
2855 unsigned char *count_str;
2856 struct directory_search_context context;
2858 if (!playlist)
2859 playlist = &current_playlist;
2861 if (check_control(playlist) < 0)
2863 splash(HZ*2, ID2P(LANG_PLAYLIST_CONTROL_ACCESS_ERROR));
2864 return -1;
2867 if (position == PLAYLIST_REPLACE)
2869 if (playlist_remove_all_tracks(playlist) == 0)
2870 position = PLAYLIST_INSERT_LAST;
2871 else
2872 return -1;
2875 if (queue)
2876 count_str = ID2P(LANG_PLAYLIST_QUEUE_COUNT);
2877 else
2878 count_str = ID2P(LANG_PLAYLIST_INSERT_COUNT);
2880 display_playlist_count(0, count_str, false);
2882 context.playlist = playlist;
2883 context.position = position;
2884 context.queue = queue;
2885 context.count = 0;
2887 cpu_boost(true);
2889 result = playlist_directory_tracksearch(dirname, recurse,
2890 directory_search_callback, &context);
2892 sync_control(playlist, false);
2894 cpu_boost(false);
2896 display_playlist_count(context.count, count_str, true);
2898 if ((audio_status() & AUDIO_STATUS_PLAY) && playlist->started)
2899 audio_flush_and_reload_tracks();
2901 #ifdef HAVE_DIRCACHE
2902 queue_post(&playlist_queue, PLAYLIST_LOAD_POINTERS, 0);
2903 #endif
2905 return result;
2909 * Insert all tracks from specified playlist into dynamic playlist.
2911 int playlist_insert_playlist(struct playlist_info* playlist, const char *filename,
2912 int position, bool queue)
2914 int fd;
2915 int max;
2916 char *temp_ptr;
2917 const char *dir;
2918 unsigned char *count_str;
2919 char temp_buf[MAX_PATH+1];
2920 char trackname[MAX_PATH+1];
2921 int count = 0;
2922 int result = 0;
2923 bool utf8 = is_m3u8(filename);
2925 if (!playlist)
2926 playlist = &current_playlist;
2928 if (check_control(playlist) < 0)
2930 splash(HZ*2, ID2P(LANG_PLAYLIST_CONTROL_ACCESS_ERROR));
2931 return -1;
2934 fd = open_utf8(filename, O_RDONLY);
2935 if (fd < 0)
2937 splash(HZ*2, ID2P(LANG_PLAYLIST_ACCESS_ERROR));
2938 return -1;
2941 /* we need the directory name for formatting purposes */
2942 dir = filename;
2944 temp_ptr = strrchr(filename+1,'/');
2945 if (temp_ptr)
2946 *temp_ptr = 0;
2947 else
2948 dir = "/";
2950 if (queue)
2951 count_str = ID2P(LANG_PLAYLIST_QUEUE_COUNT);
2952 else
2953 count_str = ID2P(LANG_PLAYLIST_INSERT_COUNT);
2955 display_playlist_count(count, count_str, false);
2957 if (position == PLAYLIST_REPLACE)
2959 if (playlist_remove_all_tracks(playlist) == 0)
2960 position = PLAYLIST_INSERT_LAST;
2961 else return -1;
2964 cpu_boost(true);
2966 while ((max = read_line(fd, temp_buf, sizeof(temp_buf))) > 0)
2968 /* user abort */
2969 if (action_userabort(TIMEOUT_NOBLOCK))
2970 break;
2972 if (temp_buf[0] != '#' && temp_buf[0] != '\0')
2974 int insert_pos;
2976 if (!utf8)
2978 /* Use trackname as a temporay buffer. Note that trackname must
2979 * be as large as temp_buf.
2981 max = convert_m3u(temp_buf, max, sizeof(temp_buf), trackname);
2984 /* we need to format so that relative paths are correctly
2985 handled */
2986 if (format_track_path(trackname, temp_buf, sizeof(trackname), max,
2987 dir) < 0)
2989 result = -1;
2990 break;
2993 insert_pos = add_track_to_playlist(playlist, trackname, position,
2994 queue, -1);
2996 if (insert_pos < 0)
2998 result = -1;
2999 break;
3002 /* Make sure tracks are inserted in correct order if user
3003 requests INSERT_FIRST */
3004 if (position == PLAYLIST_INSERT_FIRST || position >= 0)
3005 position = insert_pos + 1;
3007 count++;
3009 if ((count%PLAYLIST_DISPLAY_COUNT) == 0)
3011 display_playlist_count(count, count_str, false);
3013 if (count == PLAYLIST_DISPLAY_COUNT &&
3014 (audio_status() & AUDIO_STATUS_PLAY) &&
3015 playlist->started)
3016 audio_flush_and_reload_tracks();
3020 /* let the other threads work */
3021 yield();
3024 close(fd);
3026 if (temp_ptr)
3027 *temp_ptr = '/';
3029 sync_control(playlist, false);
3031 cpu_boost(false);
3033 display_playlist_count(count, count_str, true);
3035 if ((audio_status() & AUDIO_STATUS_PLAY) && playlist->started)
3036 audio_flush_and_reload_tracks();
3038 #ifdef HAVE_DIRCACHE
3039 queue_post(&playlist_queue, PLAYLIST_LOAD_POINTERS, 0);
3040 #endif
3042 return result;
3046 * Delete track at specified index. If index is PLAYLIST_DELETE_CURRENT then
3047 * we want to delete the current playing track.
3049 int playlist_delete(struct playlist_info* playlist, int index)
3051 int result = 0;
3053 if (!playlist)
3054 playlist = &current_playlist;
3056 if (check_control(playlist) < 0)
3058 splash(HZ*2, ID2P(LANG_PLAYLIST_CONTROL_ACCESS_ERROR));
3059 return -1;
3062 if (index == PLAYLIST_DELETE_CURRENT)
3063 index = playlist->index;
3065 result = remove_track_from_playlist(playlist, index, true);
3067 if (result != -1 && (audio_status() & AUDIO_STATUS_PLAY) &&
3068 playlist->started)
3069 audio_flush_and_reload_tracks();
3071 return result;
3075 * Move track at index to new_index. Tracks between the two are shifted
3076 * appropriately. Returns 0 on success and -1 on failure.
3078 int playlist_move(struct playlist_info* playlist, int index, int new_index)
3080 int result;
3081 int seek;
3082 bool control_file;
3083 bool queue;
3084 bool current = false;
3085 int r;
3086 char filename[MAX_PATH];
3088 if (!playlist)
3089 playlist = &current_playlist;
3091 if (check_control(playlist) < 0)
3093 splash(HZ*2, ID2P(LANG_PLAYLIST_CONTROL_ACCESS_ERROR));
3094 return -1;
3097 if (index == new_index)
3098 return -1;
3100 if (index == playlist->index)
3101 /* Moving the current track */
3102 current = true;
3104 control_file = playlist->indices[index] & PLAYLIST_INSERT_TYPE_MASK;
3105 queue = playlist->indices[index] & PLAYLIST_QUEUE_MASK;
3106 seek = playlist->indices[index] & PLAYLIST_SEEK_MASK;
3108 if (get_filename(playlist, index, seek, control_file, filename,
3109 sizeof(filename)) < 0)
3110 return -1;
3112 /* We want to insert the track at the position that was specified by
3113 new_index. This may be different then new_index because of the
3114 shifting that will occur after the delete.
3115 We calculate this before we do the remove as it depends on the
3116 size of the playlist before the track removal */
3117 r = rotate_index(playlist, new_index);
3119 /* Delete track from original position */
3120 result = remove_track_from_playlist(playlist, index, true);
3122 if (result != -1)
3124 if (r == 0)
3125 /* First index */
3126 new_index = PLAYLIST_PREPEND;
3127 else if (r == playlist->amount)
3128 /* Append */
3129 new_index = PLAYLIST_INSERT_LAST;
3130 else
3131 /* Calculate index of desired position */
3132 new_index = (r+playlist->first_index)%playlist->amount;
3134 result = add_track_to_playlist(playlist, filename, new_index, queue,
3135 -1);
3137 if (result != -1)
3139 if (current)
3141 /* Moved the current track */
3142 switch (new_index)
3144 case PLAYLIST_PREPEND:
3145 playlist->index = playlist->first_index;
3146 break;
3147 case PLAYLIST_INSERT_LAST:
3148 playlist->index = playlist->first_index - 1;
3149 if (playlist->index < 0)
3150 playlist->index += playlist->amount;
3151 break;
3152 default:
3153 playlist->index = new_index;
3154 break;
3158 if ((audio_status() & AUDIO_STATUS_PLAY) && playlist->started)
3159 audio_flush_and_reload_tracks();
3163 #ifdef HAVE_DIRCACHE
3164 queue_post(&playlist_queue, PLAYLIST_LOAD_POINTERS, 0);
3165 #endif
3167 return result;
3170 /* shuffle currently playing playlist */
3171 int playlist_randomise(struct playlist_info* playlist, unsigned int seed,
3172 bool start_current)
3174 int result;
3176 if (!playlist)
3177 playlist = &current_playlist;
3179 check_control(playlist);
3181 result = randomise_playlist(playlist, seed, start_current, true);
3183 if (result != -1 && (audio_status() & AUDIO_STATUS_PLAY) &&
3184 playlist->started)
3185 audio_flush_and_reload_tracks();
3187 return result;
3190 /* sort currently playing playlist */
3191 int playlist_sort(struct playlist_info* playlist, bool start_current)
3193 int result;
3195 if (!playlist)
3196 playlist = &current_playlist;
3198 check_control(playlist);
3200 result = sort_playlist(playlist, start_current, true);
3202 if (result != -1 && (audio_status() & AUDIO_STATUS_PLAY) &&
3203 playlist->started)
3204 audio_flush_and_reload_tracks();
3206 return result;
3209 /* returns true if playlist has been modified */
3210 bool playlist_modified(const struct playlist_info* playlist)
3212 if (!playlist)
3213 playlist = &current_playlist;
3215 if (playlist->shuffle_modified ||
3216 playlist->deleted ||
3217 playlist->num_inserted_tracks > 0)
3218 return true;
3220 return false;
3223 /* returns index of first track in playlist */
3224 int playlist_get_first_index(const struct playlist_info* playlist)
3226 if (!playlist)
3227 playlist = &current_playlist;
3229 return playlist->first_index;
3232 /* returns shuffle seed of playlist */
3233 int playlist_get_seed(const struct playlist_info* playlist)
3235 if (!playlist)
3236 playlist = &current_playlist;
3238 return playlist->seed;
3241 /* returns number of tracks in playlist (includes queued/inserted tracks) */
3242 int playlist_amount_ex(const struct playlist_info* playlist)
3244 if (!playlist)
3245 playlist = &current_playlist;
3247 return playlist->amount;
3250 /* returns full path of playlist (minus extension) */
3251 char *playlist_name(const struct playlist_info* playlist, char *buf,
3252 int buf_size)
3254 char *sep;
3256 if (!playlist)
3257 playlist = &current_playlist;
3259 strlcpy(buf, playlist->filename+playlist->dirlen, buf_size);
3261 if (!buf[0])
3262 return NULL;
3264 /* Remove extension */
3265 sep = strrchr(buf, '.');
3266 if (sep)
3267 *sep = 0;
3269 return buf;
3272 /* returns the playlist filename */
3273 char *playlist_get_name(const struct playlist_info* playlist, char *buf,
3274 int buf_size)
3276 if (!playlist)
3277 playlist = &current_playlist;
3279 strlcpy(buf, playlist->filename, buf_size);
3281 if (!buf[0])
3282 return NULL;
3284 return buf;
3287 /* Fills info structure with information about track at specified index.
3288 Returns 0 on success and -1 on failure */
3289 int playlist_get_track_info(struct playlist_info* playlist, int index,
3290 struct playlist_track_info* info)
3292 int seek;
3293 bool control_file;
3295 if (!playlist)
3296 playlist = &current_playlist;
3298 if (index < 0 || index >= playlist->amount)
3299 return -1;
3301 control_file = playlist->indices[index] & PLAYLIST_INSERT_TYPE_MASK;
3302 seek = playlist->indices[index] & PLAYLIST_SEEK_MASK;
3304 if (get_filename(playlist, index, seek, control_file, info->filename,
3305 sizeof(info->filename)) < 0)
3306 return -1;
3308 info->attr = 0;
3310 if (control_file)
3312 if (playlist->indices[index] & PLAYLIST_QUEUE_MASK)
3313 info->attr |= PLAYLIST_ATTR_QUEUED;
3314 else
3315 info->attr |= PLAYLIST_ATTR_INSERTED;
3319 if (playlist->indices[index] & PLAYLIST_SKIPPED)
3320 info->attr |= PLAYLIST_ATTR_SKIPPED;
3322 info->index = index;
3323 info->display_index = rotate_index(playlist, index) + 1;
3325 return 0;
3328 /* save the current dynamic playlist to specified file */
3329 int playlist_save(struct playlist_info* playlist, char *filename)
3331 int fd;
3332 int i, index;
3333 int count = 0;
3334 char path[MAX_PATH+1];
3335 char tmp_buf[MAX_PATH+1];
3336 int result = 0;
3337 bool overwrite_current = false;
3338 int* index_buf = NULL;
3340 if (!playlist)
3341 playlist = &current_playlist;
3343 if (playlist->amount <= 0)
3344 return -1;
3346 /* use current working directory as base for pathname */
3347 if (format_track_path(path, filename, sizeof(tmp_buf),
3348 strlen(filename)+1, getcwd(NULL, -1)) < 0)
3349 return -1;
3351 if (!strncmp(playlist->filename, path, strlen(path)))
3353 /* Attempting to overwrite current playlist file.*/
3355 if (playlist->buffer_size < (int)(playlist->amount * sizeof(int)))
3357 /* not enough buffer space to store updated indices */
3358 splash(HZ*2, ID2P(LANG_PLAYLIST_ACCESS_ERROR));
3359 return -1;
3362 /* in_ram buffer is unused for m3u files so we'll use for storing
3363 updated indices */
3364 index_buf = (int*)playlist->buffer;
3366 /* use temporary pathname */
3367 snprintf(path, sizeof(path), "%s_temp", playlist->filename);
3368 overwrite_current = true;
3371 if (is_m3u8(path))
3373 fd = open_utf8(path, O_CREAT|O_WRONLY|O_TRUNC);
3375 else
3377 /* some applications require a BOM to read the file properly */
3378 fd = open(path, O_CREAT|O_WRONLY|O_TRUNC);
3380 if (fd < 0)
3382 splash(HZ*2, ID2P(LANG_PLAYLIST_ACCESS_ERROR));
3383 return -1;
3386 display_playlist_count(count, ID2P(LANG_PLAYLIST_SAVE_COUNT), false);
3388 cpu_boost(true);
3390 index = playlist->first_index;
3391 for (i=0; i<playlist->amount; i++)
3393 bool control_file;
3394 bool queue;
3395 int seek;
3397 /* user abort */
3398 if (action_userabort(TIMEOUT_NOBLOCK))
3400 result = -1;
3401 break;
3404 control_file = playlist->indices[index] & PLAYLIST_INSERT_TYPE_MASK;
3405 queue = playlist->indices[index] & PLAYLIST_QUEUE_MASK;
3406 seek = playlist->indices[index] & PLAYLIST_SEEK_MASK;
3408 /* Don't save queued files */
3409 if (!queue)
3411 if (get_filename(playlist, index, seek, control_file, tmp_buf,
3412 MAX_PATH+1) < 0)
3414 result = -1;
3415 break;
3418 if (overwrite_current)
3419 index_buf[count] = lseek(fd, 0, SEEK_CUR);
3421 if (fdprintf(fd, "%s\n", tmp_buf) < 0)
3423 splash(HZ*2, ID2P(LANG_PLAYLIST_ACCESS_ERROR));
3424 result = -1;
3425 break;
3428 count++;
3430 if ((count % PLAYLIST_DISPLAY_COUNT) == 0)
3431 display_playlist_count(count, ID2P(LANG_PLAYLIST_SAVE_COUNT),
3432 false);
3434 yield();
3437 index = (index+1)%playlist->amount;
3440 display_playlist_count(count, ID2P(LANG_PLAYLIST_SAVE_COUNT), true);
3442 close(fd);
3444 if (overwrite_current && result >= 0)
3446 result = -1;
3448 mutex_lock(&playlist->control_mutex);
3450 /* Replace the current playlist with the new one and update indices */
3451 close(playlist->fd);
3452 if (remove(playlist->filename) >= 0)
3454 if (rename(path, playlist->filename) >= 0)
3456 playlist->fd = open_utf8(playlist->filename, O_RDONLY);
3457 if (playlist->fd >= 0)
3459 index = playlist->first_index;
3460 for (i=0, count=0; i<playlist->amount; i++)
3462 if (!(playlist->indices[index] & PLAYLIST_QUEUE_MASK))
3464 playlist->indices[index] = index_buf[count];
3465 count++;
3467 index = (index+1)%playlist->amount;
3470 /* we need to recreate control because inserted tracks are
3471 now part of the playlist and shuffle has been
3472 invalidated */
3473 result = recreate_control(playlist);
3478 mutex_unlock(&playlist->control_mutex);
3482 cpu_boost(false);
3484 return result;
3488 * Search specified directory for tracks and notify via callback. May be
3489 * called recursively.
3491 int playlist_directory_tracksearch(const char* dirname, bool recurse,
3492 int (*callback)(char*, void*),
3493 void* context)
3495 char buf[MAX_PATH+1];
3496 int result = 0;
3497 int num_files = 0;
3498 int i;
3499 struct entry *files;
3500 struct tree_context* tc = tree_get_context();
3501 int old_dirfilter = *(tc->dirfilter);
3503 if (!callback)
3504 return -1;
3506 /* use the tree browser dircache to load files */
3507 *(tc->dirfilter) = SHOW_ALL;
3509 if (ft_load(tc, dirname) < 0)
3511 splash(HZ*2, ID2P(LANG_PLAYLIST_DIRECTORY_ACCESS_ERROR));
3512 *(tc->dirfilter) = old_dirfilter;
3513 return -1;
3516 files = (struct entry*) tc->dircache;
3517 num_files = tc->filesindir;
3519 /* we've overwritten the dircache so tree browser will need to be
3520 reloaded */
3521 reload_directory();
3523 for (i=0; i<num_files; i++)
3525 /* user abort */
3526 if (action_userabort(TIMEOUT_NOBLOCK))
3528 result = -1;
3529 break;
3532 if (files[i].attr & ATTR_DIRECTORY)
3534 if (recurse)
3536 /* recursively add directories */
3537 snprintf(buf, sizeof(buf), "%s/%s", dirname, files[i].name);
3538 result = playlist_directory_tracksearch(buf, recurse,
3539 callback, context);
3540 if (result < 0)
3541 break;
3543 /* we now need to reload our current directory */
3544 if(ft_load(tc, dirname) < 0)
3546 result = -1;
3547 break;
3550 files = (struct entry*) tc->dircache;
3551 num_files = tc->filesindir;
3552 if (!num_files)
3554 result = -1;
3555 break;
3558 else
3559 continue;
3561 else if ((files[i].attr & FILE_ATTR_MASK) == FILE_ATTR_AUDIO)
3563 snprintf(buf, sizeof(buf), "%s/%s", dirname, files[i].name);
3565 if (callback(buf, context) != 0)
3567 result = -1;
3568 break;
3571 /* let the other threads work */
3572 yield();
3576 /* restore dirfilter */
3577 *(tc->dirfilter) = old_dirfilter;
3579 return result;