diff options
author | William Wilgus <wilgus.william@gmail.com> | 2022-12-03 06:21:52 -0500 |
---|---|---|
committer | William Wilgus <me.theuser@yahoo.com> | 2022-12-03 06:28:48 -0500 |
commit | 03c225fe54a2ae4699f92473ea21da11ba043148 (patch) | |
tree | ef9513da39eb6a75905516d6633223be5116cbb0 | |
parent | 90dc64da322f428b058740a9e1847cf469a3bdea (diff) | |
download | rockbox-03c225fe54.tar.gz rockbox-03c225fe54.zip |
playlist.c clean-up and organize
No functional changes
Change-Id: I5c7a4a63c54bc867b7d6c2ca6cbc8ef135e01a90
-rw-r--r-- | apps/playlist.c | 3753 | ||||
-rw-r--r-- | apps/playlist.h | 37 |
2 files changed, 1881 insertions, 1909 deletions
diff --git a/apps/playlist.c b/apps/playlist.c index d71257a515..23195b4417 100644 --- a/apps/playlist.c +++ b/apps/playlist.c @@ -29,7 +29,7 @@ directory, there will be no playlist file. 2. Control file : This file is automatically created when a playlist is started and contains all the commands done to it. - + The first non-comment line in a control file must begin with "P:VERSION:DIR:FILE" where VERSION is the playlist control file version, DIR is the directory where the playlist is located and FILE is the @@ -68,8 +68,8 @@ */ #include <stdio.h> -#include <stdlib.h> -#include <ctype.h> +#include <stdlib.h> +#include <ctype.h> #include "string-extra.h" #include "playlist.h" #include "ata_idle_notify.h" @@ -127,14 +127,15 @@ /* default load buffer size (should be at least 1 KiB) */ #define PLAYLIST_LOAD_BUFLEN (32*1024) - #define PLAYLIST_CONTROL_FILE_VERSION 2 +#define PLAYLIST_COMMAND_SIZE (MAX_PATH+12) + /* Each playlist index has a flag associated with it which identifies what type of track it is. These flags are stored in the 4 high order bits of the index. - + NOTE: This limits the playlist file size to a max of 256M. Bits 31-30: @@ -169,56 +170,12 @@ struct directory_search_context { static struct playlist_info current_playlist; -static void empty_playlist(struct playlist_info* playlist, bool resume); -static void new_playlist(struct playlist_info* playlist, const char *dir, - const char *file); -static void create_control(struct playlist_info* playlist); -static int check_control(struct playlist_info* playlist); -static int recreate_control(struct playlist_info* playlist); -static void update_playlist_filename(struct playlist_info* playlist, - const char *dir, const char *file); -static int add_indices_to_playlist(struct playlist_info* playlist, - char* buffer, size_t buflen); -static int add_track_to_playlist(struct playlist_info* playlist, - const char *filename, int position, - bool queue, int seek_pos); -static int directory_search_callback(char* filename, void* context); -static int remove_track_from_playlist(struct playlist_info* playlist, - int position, bool write); -static int randomise_playlist(struct playlist_info* playlist, - unsigned int seed, bool start_current, - bool write); -static int sort_playlist(struct playlist_info* playlist, bool start_current, - bool write); -static int get_next_index(const struct playlist_info* playlist, int steps, - int repeat_mode); -static void find_and_set_playlist_index(struct playlist_info* playlist, - unsigned int seek); -static int compare(const void* p1, const void* p2); -static int get_filename(struct playlist_info* playlist, int index, int seek, - bool control_file, char *buf, int buf_length); -static int get_next_directory(char *dir); -static int get_next_dir(char *dir, bool is_forward); -static int get_previous_directory(char *dir); -static int check_subdir_for_music(char *dir, const char *subdir, bool recurse); -static ssize_t format_track_path(char *dest, char *src, int buf_length, - const char *dir); -static void display_playlist_count(int count, const unsigned char *fmt, - bool final); -static void display_buffer_full(void); -static int flush_cached_control(struct playlist_info* playlist); -static int update_control(struct playlist_info* playlist, - enum playlist_command command, int i1, int i2, - const char* s1, const char* s2, void* data); -static void sync_control(struct playlist_info* playlist, bool force); -static int rotate_index(const struct playlist_info* playlist, int index); - #ifdef HAVE_DIRCACHE #define PLAYLIST_LOAD_POINTERS 1 static struct event_queue playlist_queue SHAREDBSS_ATTR; static long playlist_stack[(DEFAULT_STACK_SIZE + 0x800)/sizeof(long)]; -static const char playlist_thread_name[] = "playlist cachectrl"; +static const char thread_playlist_name[] = "playlist cachectrl"; #endif static struct mutex current_playlist_mutex SHAREDBSS_ATTR; @@ -253,14 +210,14 @@ static bool is_m3u8(const char* filename) } -/* Convert a filename in an M3U playlist to UTF-8. +/* Convert a filename in an M3U playlist to UTF-8. * * buf - the filename to convert; can contain more than one line from the * playlist. * buf_len - amount of buf that is used. * buf_max - total size of buf. * temp - temporary conversion buffer, at least buf_max bytes. - * + * * Returns the length of the converted filename. */ static int convert_m3u(char* buf, int buf_len, int buf_max, char* temp) @@ -279,10 +236,10 @@ static int convert_m3u(char* buf, int buf_len, int buf_max, char* temp) { i--; } - + buf_len = i; dest = temp; - + /* Convert char by char, so as to not overflow temp (iso_decode should * preferably handle this). No more than 4 bytes should be generated for * each input char. @@ -291,13 +248,222 @@ static int convert_m3u(char* buf, int buf_len, int buf_max, char* temp) { dest = iso_decode(&buf[i], dest, -1, 1); } - + *dest = 0; strcpy(buf, temp); return dest - temp; } /* + * create control file for playlist + */ +static void create_control(struct playlist_info* playlist) +{ + playlist->control_fd = open(playlist->control_filename, + O_CREAT|O_RDWR|O_TRUNC, 0666); + if (playlist->control_fd < 0) + { + if (check_rockboxdir()) + { + cond_talk_ids_fq(LANG_PLAYLIST_CONTROL_ACCESS_ERROR); + splashf(HZ*2, (unsigned char *)"%s (%d)", + str(LANG_PLAYLIST_CONTROL_ACCESS_ERROR), + playlist->control_fd); + } + playlist->control_created = false; + } + else + { + playlist->control_created = true; + } +} + +/* + * Rotate indices such that first_index is index 0 + */ +static int rotate_index(const struct playlist_info* playlist, int index) +{ + index -= playlist->first_index; + if (index < 0) + index += playlist->amount; + + return index; +} + +/* + * sync control file to disk + */ +static void sync_control(struct playlist_info* playlist, bool force) +{ +#ifdef HAVE_DIRCACHE + if (playlist->started && force) +#else + (void) force; + + if (playlist->started) +#endif + { + if (playlist->pending_control_sync) + { + mutex_lock(playlist->control_mutex); + fsync(playlist->control_fd); + playlist->pending_control_sync = false; + mutex_unlock(playlist->control_mutex); + } + } +} + +/* + * Flush any cached control commands to disk. Called when playlist is being + * modified. Returns 0 on success and -1 on failure. + */ +static int flush_cached_control(struct playlist_info* playlist) +{ + int result = 0; + int i; + + if (!playlist->num_cached) + return 0; + + lseek(playlist->control_fd, 0, SEEK_END); + + for (i=0; i<playlist->num_cached; i++) + { + struct playlist_control_cache* cache = + &(playlist->control_cache[i]); + + switch (cache->command) + { + case PLAYLIST_COMMAND_PLAYLIST: + result = fdprintf(playlist->control_fd, "P:%d:%s:%s\n", + cache->i1, cache->s1, cache->s2); + break; + case PLAYLIST_COMMAND_ADD: + case PLAYLIST_COMMAND_QUEUE: + result = fdprintf(playlist->control_fd, "%c:%d:%d:", + (cache->command == PLAYLIST_COMMAND_ADD)?'A':'Q', + cache->i1, cache->i2); + if (result > 0) + { + /* save the position in file where name is written */ + int* seek_pos = (int *)cache->data; + *seek_pos = lseek(playlist->control_fd, 0, SEEK_CUR); + result = fdprintf(playlist->control_fd, "%s\n", + cache->s1); + } + break; + case PLAYLIST_COMMAND_DELETE: + result = fdprintf(playlist->control_fd, "D:%d\n", cache->i1); + break; + case PLAYLIST_COMMAND_SHUFFLE: + result = fdprintf(playlist->control_fd, "S:%d:%d\n", + cache->i1, cache->i2); + break; + case PLAYLIST_COMMAND_UNSHUFFLE: + result = fdprintf(playlist->control_fd, "U:%d\n", cache->i1); + break; + case PLAYLIST_COMMAND_RESET: + result = fdprintf(playlist->control_fd, "R\n"); + break; + default: + break; + } + + if (result <= 0) + break; + } + + if (result > 0) + { + playlist->num_cached = 0; + playlist->pending_control_sync = true; + + result = 0; + } + else + { + result = -1; + splash(HZ*2, ID2P(LANG_PLAYLIST_CONTROL_UPDATE_ERROR)); + } + + return result; +} + +/* + * Update control data with new command. Depending on the command, it may be + * cached or flushed to disk. + */ +static int update_control(struct playlist_info* playlist, + enum playlist_command command, int i1, int i2, + const char* s1, const char* s2, void* data) +{ + int result = 0; + struct playlist_control_cache* cache; + bool flush = false; + + mutex_lock(playlist->control_mutex); + + cache = &(playlist->control_cache[playlist->num_cached++]); + + cache->command = command; + cache->i1 = i1; + cache->i2 = i2; + cache->s1 = s1; + cache->s2 = s2; + cache->data = data; + + switch (command) + { + case PLAYLIST_COMMAND_PLAYLIST: + case PLAYLIST_COMMAND_ADD: + case PLAYLIST_COMMAND_QUEUE: +#ifndef HAVE_DIRCACHE + case PLAYLIST_COMMAND_DELETE: + case PLAYLIST_COMMAND_RESET: +#endif + flush = true; + break; + case PLAYLIST_COMMAND_SHUFFLE: + case PLAYLIST_COMMAND_UNSHUFFLE: + default: + /* only flush when needed */ + break; + } + + if (flush || playlist->num_cached == PLAYLIST_MAX_CACHE) + result = flush_cached_control(playlist); + + mutex_unlock(playlist->control_mutex); + + return result; +} + +/* + * store directory and name of playlist file + */ +static void update_playlist_filename(struct playlist_info* playlist, + const char *dir, const char *file) +{ + char *sep=""; + int dirlen = strlen(dir); + + playlist->utf8 = is_m3u8(file); + + /* If the dir does not end in trailing slash, we use a separator. + Otherwise we don't. */ + if(!dirlen || '/' != dir[dirlen-1]) + { + sep="/"; + dirlen++; + } + + playlist->dirlen = dirlen; + + snprintf(playlist->filename, sizeof(playlist->filename), + "%s%s%s", dir, sep, file); +} + +/* * remove any files and indices associated with the playlist */ static void empty_playlist(struct playlist_info* playlist, bool resume) @@ -344,6 +510,73 @@ static void empty_playlist(struct playlist_info* playlist, bool resume) } /* + * Returns absolute path of track + * + * dest: output buffer + * src: the file name from the playlist + * dir: the absolute path to the directory where the playlist resides + * + * The type of path in "src" determines what will be written to "dest": + * + * 1. UNIX-style absolute paths (/foo/bar) remain unaltered + * 2. Windows-style absolute paths (C:/foo/bar) will be converted into an + * absolute path by replacing the drive letter with the volume that the + * *playlist* resides on, ie. the volume in "dir" + * 3. Relative paths are converted to absolute paths by prepending "dir". + * This also applies to Windows-style relative paths "C:foo/bar" where + * the drive letter is accepted but ignored. + */ +static ssize_t format_track_path(char *dest, char *src, int buf_length, + const char *dir) +{ + size_t len = 0; + + /* Look for the end of the string */ + while (1) + { + int c = src[len]; + if (c == '\n' || c == '\r' || c == '\0') + break; + len++; + } + + /* Now work back killing white space */ + while (len > 0) + { + int c = src[len - 1]; + if (c != '\t' && c != ' ') + break; + len--; + } + + src[len] = '\0'; + + /* Replace backslashes with forward slashes */ + path_correct_separators(src, src); + + /* Handle Windows-style absolute paths */ + if (path_strip_drive(src, (const char **)&src, true) >= 0 && + src[-1] == PATH_SEPCH) + { + #ifdef HAVE_MULTIVOLUME + const char *p; + path_strip_last_volume(dir, &p, false); + dir = strmemdupa(dir, p - dir); /* empty if no volspec on dir */ + #else + dir = ""; /* only volume is root */ + #endif + } + + len = path_append(dest, *dir ? dir : PATH_ROOTSTR, src, buf_length); + if (len >= (size_t)buf_length) + return -1; /* buffer too small */ + + path_remove_dot_segments (dest, dest); + + return strlen (dest); +} + +/* * Initialize a new playlist for viewing/editing/playing. dir is the * directory where the playlist is located and file is the filename. */ @@ -363,7 +596,7 @@ static void new_playlist(struct playlist_info* playlist, const char *dir, else dirused = ""; /* empty playlist */ } - + update_playlist_filename(playlist, dirused, fileused); if (playlist->control_fd >= 0) @@ -375,30 +608,6 @@ static void new_playlist(struct playlist_info* playlist, const char *dir, } /* - * create control file for playlist - */ -static void create_control(struct playlist_info* playlist) -{ - playlist->control_fd = open(playlist->control_filename, - O_CREAT|O_RDWR|O_TRUNC, 0666); - if (playlist->control_fd < 0) - { - if (check_rockboxdir()) - { - cond_talk_ids_fq(LANG_PLAYLIST_CONTROL_ACCESS_ERROR); - splashf(HZ*2, (unsigned char *)"%s (%d)", - str(LANG_PLAYLIST_CONTROL_ACCESS_ERROR), - playlist->control_fd); - } - playlist->control_created = false; - } - else - { - playlist->control_created = true; - } -} - -/* * validate the control file. This may include creating/initializing it if * necessary; */ @@ -429,6 +638,40 @@ static int check_control(struct playlist_info* playlist) return 0; } + +/* + * Display buffer full message + */ +static void display_buffer_full(void) +{ + splash(HZ*2, ID2P(LANG_PLAYLIST_BUFFER_FULL)); +} + +/* + * Display splash message showing progress of playlist/directory insertion or + * save. + */ +static void display_playlist_count(int count, const unsigned char *fmt, + bool final) +{ + static long talked_tick = 0; + long id = P2ID(fmt); + if(global_settings.talk_menu && id>=0) + { + if(final || (count && (talked_tick == 0 + || TIME_AFTER(current_tick, talked_tick+5*HZ)))) + { + talked_tick = current_tick; + talk_number(count, false); + talk_id(id, true); + } + } + fmt = P2STR(fmt); + + splashf(0, fmt, count, str(LANG_OFF_ABORT)); +} + + /* * recreate the control file based on current playlist entries */ @@ -532,31 +775,6 @@ static int recreate_control(struct playlist_info* playlist) } /* - * store directory and name of playlist file - */ -static void update_playlist_filename(struct playlist_info* playlist, - const char *dir, const char *file) -{ - char *sep=""; - int dirlen = strlen(dir); - - playlist->utf8 = is_m3u8(file); - - /* If the dir does not end in trailing slash, we use a separator. - Otherwise we don't. */ - if(!dirlen || '/' != dir[dirlen-1]) - { - sep="/"; - dirlen++; - } - - playlist->dirlen = dirlen; - - snprintf(playlist->filename, sizeof(playlist->filename), - "%s%s%s", dir, sep, file); -} - -/* * calculate track offsets within a playlist file */ static int add_indices_to_playlist(struct playlist_info* playlist, @@ -588,7 +806,7 @@ static int add_indices_to_playlist(struct playlist_info* playlist, i = lseek(playlist->fd, playlist->utf8 ? BOM_UTF_8_SIZE : 0, SEEK_SET); playlist_fd = playlist->fd; - playlist->fd = -2; /* DEBUGGING Remove me! */ + playlist->fd = -2; /* DEBUGGING Remove me! */ splash(0, ID2P(LANG_WAIT)); @@ -642,11 +860,11 @@ static int add_indices_to_playlist(struct playlist_info* playlist, } exit: -/* v DEBUGGING Remove me! */ - if (playlist->fd != -2) - splashf(HZ, "Error playlist fd"); - playlist->fd = playlist_fd; -/* ^ DEBUGGING Remove me! */ +/* v DEBUGGING Remove me! */ + if (playlist->fd != -2) + splashf(HZ, "Error playlist fd"); + playlist->fd = playlist_fd; +/* ^ DEBUGGING Remove me! */ playlist->amount = amount; playlist->max_playlist_size = max_playlist_size; mutex_unlock(playlist->control_mutex); @@ -657,6 +875,372 @@ exit: return result; } + +/* + * Checks if there are any music files in the dir or any of its + * subdirectories. May be called recursively. + */ +static int check_subdir_for_music(char *dir, const char *subdir, bool recurse) +{ + int result = -1; + size_t dirlen = strlen(dir); + int num_files = 0; + int i; + struct entry *files; + bool has_music = false; + bool has_subdir = false; + struct tree_context* tc = tree_get_context(); + + if (path_append(dir + dirlen, PA_SEP_HARD, subdir, MAX_PATH - dirlen) >= + MAX_PATH - dirlen) + { + return 0; + } + + if (ft_load(tc, dir) < 0) + { + return -2; + } + + tree_lock_cache(tc); + files = tree_get_entries(tc); + num_files = tc->filesindir; + + for (i=0; i<num_files; i++) + { + if (files[i].attr & ATTR_DIRECTORY) + has_subdir = true; + else if ((files[i].attr & FILE_ATTR_MASK) == FILE_ATTR_AUDIO) + { + has_music = true; + break; + } + } + + if (has_music) + { + tree_unlock_cache(tc); + return 0; + } + + if (has_subdir && recurse) + { + for (i=0; i<num_files; i++) + { + if (action_userabort(TIMEOUT_NOBLOCK)) + { + result = -2; + break; + } + + if (files[i].attr & ATTR_DIRECTORY) + { + result = check_subdir_for_music(dir, files[i].name, true); + if (!result) + break; + } + } + } + tree_unlock_cache(tc); + + if (result < 0) + { + if (dirlen) + { + dir[dirlen] = '\0'; + } + else + { + strcpy(dir, PATH_ROOTSTR); + } + + /* we now need to reload our current directory */ + if(ft_load(tc, dir) < 0) + splash(HZ*2, ID2P(LANG_PLAYLIST_DIRECTORY_ACCESS_ERROR)); + } + return result; +} + +/* + * search through all the directories (starting with the current) to find + * one that has tracks to play + */ +static int get_next_dir(char *dir, bool is_forward) +{ + struct playlist_info* playlist = ¤t_playlist; + int result = -1; + char *start_dir = NULL; + bool exit = false; + struct tree_context* tc = tree_get_context(); + int saved_dirfilter = *(tc->dirfilter); + unsigned int base_len; + + if (global_settings.constrain_next_folder) + { + /* constrain results to directories below user's start directory */ + strcpy(dir, global_settings.start_directory); + base_len = strlen(dir); + + /* strip any trailing slash from base directory */ + if (base_len > 0 && dir[base_len - 1] == '/') + { + base_len--; + dir[base_len] = '\0'; + } + } + else + { + /* start from root directory */ + dir[0] = '\0'; + base_len = 0; + } + + /* process random folder advance */ + if (global_settings.next_folder == FOLDER_ADVANCE_RANDOM) + { + int fd = open(ROCKBOX_DIR "/folder_advance_list.dat", O_RDONLY); + if (fd >= 0) + { + int folder_count = 0; + ssize_t nread = read(fd,&folder_count,sizeof(int)); + if ((nread == sizeof(int)) && folder_count) + { + char buffer[MAX_PATH]; + /* give up looking for a directory after we've had four + times as many tries as there are directories. */ + unsigned long allowed_tries = folder_count * 4; + int i; + srand(current_tick); + *(tc->dirfilter) = SHOW_MUSIC; + tc->sort_dir = global_settings.sort_dir; + while (!exit && allowed_tries--) + { + i = rand() % folder_count; + lseek(fd, sizeof(int) + (MAX_PATH * i), SEEK_SET); + read(fd, buffer, MAX_PATH); + /* is the current dir within our base dir and has music? */ + if ((base_len == 0 || !strncmp(buffer, dir, base_len)) + && check_subdir_for_music(buffer, "", false) == 0) + exit = true; + } + close(fd); + *(tc->dirfilter) = saved_dirfilter; + tc->sort_dir = global_settings.sort_dir; + reload_directory(); + if (exit) + { + strcpy(dir,buffer); + return 0; + } + } + else + close(fd); + } + } + + /* if the current file is within our base dir, use its dir instead */ + if (base_len == 0 || !strncmp(playlist->filename, dir, base_len)) + strmemccpy(dir, playlist->filename, playlist->dirlen); + + /* use the tree browser dircache to load files */ + *(tc->dirfilter) = SHOW_ALL; + + /* set up sorting/direction */ + tc->sort_dir = global_settings.sort_dir; + if (!is_forward) + { + static const char sortpairs[] = + { + [SORT_ALPHA] = SORT_ALPHA_REVERSED, + [SORT_DATE] = SORT_DATE_REVERSED, + [SORT_TYPE] = SORT_TYPE_REVERSED, + [SORT_ALPHA_REVERSED] = SORT_ALPHA, + [SORT_DATE_REVERSED] = SORT_DATE, + [SORT_TYPE_REVERSED] = SORT_TYPE, + }; + + if ((unsigned)tc->sort_dir < sizeof(sortpairs)) + tc->sort_dir = sortpairs[tc->sort_dir]; + } + + while (!exit) + { + struct entry *files; + int num_files = 0; + int i; + + if (ft_load(tc, (dir[0]=='\0')?"/":dir) < 0) + { + exit = true; + result = -1; + break; + } + + tree_lock_cache(tc); + files = tree_get_entries(tc); + num_files = tc->filesindir; + + for (i=0; i<num_files; i++) + { + /* user abort */ + if (action_userabort(TIMEOUT_NOBLOCK)) + { + result = -1; + exit = true; + break; + } + + if (files[i].attr & ATTR_DIRECTORY) + { + if (!start_dir) + { + result = check_subdir_for_music(dir, files[i].name, true); + if (result != -1) + { + exit = true; + break; + } + } + else if (!strcmp(start_dir, files[i].name)) + start_dir = NULL; + } + } + tree_unlock_cache(tc); + + if (!exit) + { + /* we've already descended to the base dir with nothing found, + check whether that contains music */ + if (strlen(dir) <= base_len) + { + result = check_subdir_for_music(dir, "", true); + if (result == -1) + /* there's no music files in the base directory, + treat as a fatal error */ + result = -2; + break; + } + else + { + /* move down to parent directory. current directory name is + stored as the starting point for the search in parent */ + start_dir = strrchr(dir, '/'); + if (start_dir) + { + *start_dir = '\0'; + start_dir++; + } + else + break; + } + } + } + + /* restore dirfilter */ + *(tc->dirfilter) = saved_dirfilter; + tc->sort_dir = global_settings.sort_dir; + + return result; +} + +static int get_next_directory(char *dir){ + return get_next_dir(dir, true); +} + +static int get_previous_directory(char *dir){ + return get_next_dir(dir, false); +} + + +/* + * gets pathname for track at seek index + */ +static int get_filename(struct playlist_info* playlist, int index, int seek, + bool control_file, char *buf, int buf_length) +{ + int fd; + int max = -1; + char tmp_buf[MAX_PATH+1]; + char dir_buf[MAX_PATH+1]; + bool utf8 = playlist->utf8; + + if (buf_length > MAX_PATH+1) + buf_length = MAX_PATH+1; + +#ifdef HAVE_DIRCACHE + if (playlist->dcfrefs) + { + max = dircache_get_fileref_path(&playlist->dcfrefs[index], + tmp_buf, sizeof(tmp_buf)); + } +#endif /* HAVE_DIRCACHE */ + + if (playlist->in_ram && !control_file && max < 0) + { + strmemccpy(tmp_buf, (char*)&playlist->buffer[seek], sizeof(tmp_buf)); + } + else if (max < 0) + { + mutex_lock(playlist->control_mutex); + + if (control_file) + { + fd = playlist->control_fd; + utf8 = true; + } + else + { + if(-1 == playlist->fd) + playlist->fd = open(playlist->filename, O_RDONLY); + + fd = playlist->fd; + } + + if(-1 != fd) + { + + if (lseek(fd, seek, SEEK_SET) != seek) + max = -1; + else + { + max = read(fd, tmp_buf, MIN((size_t) buf_length, sizeof(tmp_buf))); + + if (max > 0) + { + /* playlist file may end without a new line - terminate buffer */ + tmp_buf[MIN(max, (int)sizeof(tmp_buf) - 1)] = '\0'; + + /* Use dir_buf as a temporary buffer. Note that dir_buf must + * be as large as tmp_buf. + */ + if (!utf8) + max = convert_m3u(tmp_buf, max, sizeof(tmp_buf), dir_buf); + } + } + } + + mutex_unlock(playlist->control_mutex); + + if (max < 0) + { + if (usb_detect() == USB_INSERTED) + ; /* ignore error on usb plug */ + else if (control_file) + splash(HZ*2, ID2P(LANG_PLAYLIST_CONTROL_ACCESS_ERROR)); + else + splash(HZ*2, ID2P(LANG_PLAYLIST_ACCESS_ERROR)); + + return max; + } + } + + strmemccpy(dir_buf, playlist->filename, playlist->dirlen); + + return format_track_path(buf, tmp_buf, buf_length, dir_buf); + + (void)index; +} + /* * Utility function to create a new playlist, fill it with the next or * previous directory, shuffle it if needed, and start playback. @@ -701,33 +1285,6 @@ static int create_and_play_dir(int direction, bool play_last) } /* - * Removes all tracks, from the playlist, leaving the presently playing - * track queued. - */ -int playlist_remove_all_tracks(struct playlist_info *playlist) -{ - int result; - - if (playlist == NULL) - playlist = ¤t_playlist; - - while (playlist->index > 0) - if ((result = remove_track_from_playlist(playlist, 0, true)) < 0) - return result; - - while (playlist->amount > 1) - if ((result = remove_track_from_playlist(playlist, 1, true)) < 0) - return result; - - if (playlist->amount == 1) { - playlist->indices[0] |= PLAYLIST_QUEUED; - } - - return 0; -} - - -/* * Add track to playlist at specified position. There are seven special * positions that can be specified: * PLAYLIST_PREPEND - Add track at beginning of playlist @@ -803,16 +1360,16 @@ static int add_track_to_playlist(struct playlist_info* playlist, int offset; int n = playlist->amount - rotate_index(playlist, playlist->index); - + if (n > 0) offset = rand() % n; else offset = 0; - + position = playlist->index + offset + 1; if (position >= playlist->amount) position -= playlist->amount; - + insert_position = position; } else @@ -828,11 +1385,11 @@ static int add_track_to_playlist(struct playlist_info* playlist, case PLAYLIST_REPLACE: if (playlist_remove_all_tracks(playlist) < 0) return -1; - + playlist->last_insert_pos = position = insert_position = playlist->index + 1; break; } - + if (queue) flags |= PLAYLIST_QUEUED; @@ -845,7 +1402,7 @@ static int add_track_to_playlist(struct playlist_info* playlist, playlist->dcfrefs[i] = playlist->dcfrefs[i-1]; #endif } - + /* update stored indices if needed */ if (orig_position < 0) @@ -881,7 +1438,7 @@ static int add_track_to_playlist(struct playlist_info* playlist, playlist->amount++; playlist->num_inserted_tracks++; - + return insert_position; } @@ -918,7 +1475,7 @@ static int directory_search_callback(char* filename, void* context) count_str = ID2P(LANG_PLAYLIST_INSERT_COUNT); display_playlist_count(c->count, count_str, false); - + if ((c->count) == PLAYLIST_DISPLAY_COUNT && (audio_status() & AUDIO_STATUS_PLAY) && c->playlist->started) @@ -981,11 +1538,32 @@ static int remove_track_from_playlist(struct playlist_info* playlist, sync_control(playlist, false); } - + return 0; } /* + * Search for the seek track and set appropriate indices. Used after shuffle + * to make sure the current index is still pointing to correct track. + */ +static void find_and_set_playlist_index(struct playlist_info* playlist, + unsigned int seek) +{ + int i; + + /* Set the index to the current song */ + for (i=0; i<playlist->amount; i++) + { + if (playlist->indices[i] == seek) + { + playlist->index = playlist->first_index = i; + + break; + } + } +} + +/* * randomly rearrange the array of indices for the playlist. If start_current * is true then update the index to the new index of the current playing track */ @@ -996,7 +1574,7 @@ static int randomise_playlist(struct playlist_info* playlist, int count; int candidate; unsigned int current = playlist->indices[playlist->index]; - + /* seed 0 is used to identify sorted playlist for resume purposes */ if (seed == 0) seed = 1; @@ -1039,11 +1617,38 @@ static int randomise_playlist(struct playlist_info* playlist, update_control(playlist, PLAYLIST_COMMAND_SHUFFLE, seed, playlist->first_index, NULL, NULL, NULL); } - + return 0; } /* + * used to sort track indices. Sort order is as follows: + * 1. Prepended tracks (in prepend order) + * 2. Playlist/directory tracks (in playlist order) + * 3. Inserted/Appended tracks (in insert order) + */ +static int compare(const void* p1, const void* p2) +{ + unsigned long* e1 = (unsigned long*) p1; + unsigned long* e2 = (unsigned long*) p2; + unsigned long flags1 = *e1 & PLAYLIST_INSERT_TYPE_MASK; + unsigned long flags2 = *e2 & PLAYLIST_INSERT_TYPE_MASK; + + if (flags1 == flags2) + return (*e1 & PLAYLIST_SEEK_MASK) - (*e2 & PLAYLIST_SEEK_MASK); + else if (flags1 == PLAYLIST_INSERT_TYPE_PREPEND || + flags2 == PLAYLIST_INSERT_TYPE_APPEND) + return -1; + else if (flags1 == PLAYLIST_INSERT_TYPE_APPEND || + flags2 == PLAYLIST_INSERT_TYPE_PREPEND) + return 1; + else if (flags1 && flags2) + return (*e1 & PLAYLIST_SEEK_MASK) - (*e2 & PLAYLIST_SEEK_MASK); + else + return *e1 - *e2; +} + +/* * Sort the array of indices for the playlist. If start_current is true then * set the index to the new index of the current song. * Also while going to unshuffled mode set the first_index to 0. @@ -1079,7 +1684,7 @@ static int sort_playlist(struct playlist_info* playlist, bool start_current, update_control(playlist, PLAYLIST_COMMAND_UNSHUFFLE, playlist->first_index, -1, NULL, NULL, NULL); } - + return 0; } @@ -1091,7 +1696,7 @@ static int calculate_step_count(const struct playlist_info *playlist, int steps) int i, count, direction; int index; int stepped_count = 0; - + if (steps < 0) { direction = -1; @@ -1129,28 +1734,6 @@ static int calculate_step_count(const struct playlist_info *playlist, int steps) return steps; } -/* Marks the index of the track to be skipped that is "steps" away from - * current playing track. - */ -void playlist_skip_entry(struct playlist_info *playlist, int steps) -{ - int index; - - if (playlist == NULL) - playlist = ¤t_playlist; - - /* need to account for already skipped tracks */ - steps = calculate_step_count(playlist, steps); - - index = playlist->index + steps; - if (index < 0) - index += playlist->amount; - else if (index >= playlist->amount) - index -= playlist->amount; - - playlist->indices[index] |= PLAYLIST_SKIPPED; -} - /* * returns the index of the track that is "steps" away from current playing * track. @@ -1229,56 +1812,8 @@ static int get_next_index(const struct playlist_info* playlist, int steps, /* No luck if the whole playlist was bad. */ if (playlist->indices[next_index] & PLAYLIST_SKIPPED) return -1; - - return next_index; -} - -/* - * Search for the seek track and set appropriate indices. Used after shuffle - * to make sure the current index is still pointing to correct track. - */ -static void find_and_set_playlist_index(struct playlist_info* playlist, - unsigned int seek) -{ - int i; - - /* Set the index to the current song */ - for (i=0; i<playlist->amount; i++) - { - if (playlist->indices[i] == seek) - { - playlist->index = playlist->first_index = i; - - break; - } - } -} - -/* - * used to sort track indices. Sort order is as follows: - * 1. Prepended tracks (in prepend order) - * 2. Playlist/directory tracks (in playlist order) - * 3. Inserted/Appended tracks (in insert order) - */ -static int compare(const void* p1, const void* p2) -{ - unsigned long* e1 = (unsigned long*) p1; - unsigned long* e2 = (unsigned long*) p2; - unsigned long flags1 = *e1 & PLAYLIST_INSERT_TYPE_MASK; - unsigned long flags2 = *e2 & PLAYLIST_INSERT_TYPE_MASK; - if (flags1 == flags2) - return (*e1 & PLAYLIST_SEEK_MASK) - (*e2 & PLAYLIST_SEEK_MASK); - else if (flags1 == PLAYLIST_INSERT_TYPE_PREPEND || - flags2 == PLAYLIST_INSERT_TYPE_APPEND) - return -1; - else if (flags1 == PLAYLIST_INSERT_TYPE_APPEND || - flags2 == PLAYLIST_INSERT_TYPE_PREPEND) - return 1; - else if (flags1 && flags2) - return (*e1 & PLAYLIST_SEEK_MASK) - (*e2 & PLAYLIST_SEEK_MASK); - else - return *e1 - *e2; + return next_index; } #ifdef HAVE_DIRCACHE @@ -1287,7 +1822,7 @@ static int compare(const void* p1, const void* p2) * without affecting playlist load up performance. This thread also flushes * any pending control commands when the disk spins up. */ -static void playlist_flush_callback(void) +static void flush_playlist_callback(void) { struct playlist_info *playlist; playlist = ¤t_playlist; @@ -1303,7 +1838,7 @@ static void playlist_flush_callback(void) } } -static void playlist_thread(void) +static void thread_playlist(void) { struct queue_event ev; bool dirty_pointers = false; @@ -1340,12 +1875,12 @@ static void playlist_thread(void) if (playlist->control_fd >= 0) { if (playlist->num_cached > 0) - register_storage_idle_func(playlist_flush_callback); + register_storage_idle_func(flush_playlist_callback); } if (!playlist->dcfrefs || playlist->amount <= 0) break ; - + /* Check if previously loaded pointers are intact. */ if (!dirty_pointers) break ; @@ -1364,7 +1899,7 @@ static void playlist_thread(void) /* Process only pointers that are superficially stale. */ if (dircache_search(DCS_FILEREF, &playlist->dcfrefs[index], NULL) > 0) continue ; - + control_file = playlist->indices[index] & PLAYLIST_INSERT_TYPE_MASK; seek = playlist->indices[index] & PLAYLIST_SEEK_MASK; @@ -1387,10 +1922,10 @@ static void playlist_thread(void) if (index == playlist->amount) dirty_pointers = false; - + break ; } - + case SYS_USB_CONNECTED: usb_acknowledge(SYS_USB_CONNECTED_ACK); usb_wait_for_disconnect(&playlist_queue); @@ -1401,629 +1936,6 @@ static void playlist_thread(void) #endif /* - * gets pathname for track at seek index - */ -static int get_filename(struct playlist_info* playlist, int index, int seek, - bool control_file, char *buf, int buf_length) -{ - int fd; - int max = -1; - char tmp_buf[MAX_PATH+1]; - char dir_buf[MAX_PATH+1]; - bool utf8 = playlist->utf8; - - if (buf_length > MAX_PATH+1) - buf_length = MAX_PATH+1; - -#ifdef HAVE_DIRCACHE - if (playlist->dcfrefs) - { - max = dircache_get_fileref_path(&playlist->dcfrefs[index], - tmp_buf, sizeof(tmp_buf)); - } -#endif /* HAVE_DIRCACHE */ - - if (playlist->in_ram && !control_file && max < 0) - { - strmemccpy(tmp_buf, (char*)&playlist->buffer[seek], sizeof(tmp_buf)); - } - else if (max < 0) - { - mutex_lock(playlist->control_mutex); - - if (control_file) - { - fd = playlist->control_fd; - utf8 = true; - } - else - { - if(-1 == playlist->fd) - playlist->fd = open(playlist->filename, O_RDONLY); - - fd = playlist->fd; - } - - if(-1 != fd) - { - - if (lseek(fd, seek, SEEK_SET) != seek) - max = -1; - else - { - max = read(fd, tmp_buf, MIN((size_t) buf_length, sizeof(tmp_buf))); - - if (max > 0) - { - /* playlist file may end without a new line - terminate buffer */ - tmp_buf[MIN(max, (int)sizeof(tmp_buf) - 1)] = '\0'; - - /* Use dir_buf as a temporary buffer. Note that dir_buf must - * be as large as tmp_buf. - */ - if (!utf8) - max = convert_m3u(tmp_buf, max, sizeof(tmp_buf), dir_buf); - } - } - } - - mutex_unlock(playlist->control_mutex); - - if (max < 0) - { - if (usb_detect() == USB_INSERTED) - ; /* ignore error on usb plug */ - else if (control_file) - splash(HZ*2, ID2P(LANG_PLAYLIST_CONTROL_ACCESS_ERROR)); - else - splash(HZ*2, ID2P(LANG_PLAYLIST_ACCESS_ERROR)); - - return max; - } - } - - strmemccpy(dir_buf, playlist->filename, playlist->dirlen); - - return format_track_path(buf, tmp_buf, buf_length, dir_buf); - - (void)index; -} - -static int get_next_directory(char *dir){ - return get_next_dir(dir, true); -} - -static int get_previous_directory(char *dir){ - return get_next_dir(dir, false); -} - -/* - * search through all the directories (starting with the current) to find - * one that has tracks to play - */ -static int get_next_dir(char *dir, bool is_forward) -{ - struct playlist_info* playlist = ¤t_playlist; - int result = -1; - char *start_dir = NULL; - bool exit = false; - struct tree_context* tc = tree_get_context(); - int saved_dirfilter = *(tc->dirfilter); - unsigned int base_len; - - if (global_settings.constrain_next_folder) - { - /* constrain results to directories below user's start directory */ - strcpy(dir, global_settings.start_directory); - base_len = strlen(dir); - - /* strip any trailing slash from base directory */ - if (base_len > 0 && dir[base_len - 1] == '/') - { - base_len--; - dir[base_len] = '\0'; - } - } - else - { - /* start from root directory */ - dir[0] = '\0'; - base_len = 0; - } - - /* process random folder advance */ - if (global_settings.next_folder == FOLDER_ADVANCE_RANDOM) - { - int fd = open(ROCKBOX_DIR "/folder_advance_list.dat", O_RDONLY); - if (fd >= 0) - { - int folder_count = 0; - ssize_t nread = read(fd,&folder_count,sizeof(int)); - if ((nread == sizeof(int)) && folder_count) - { - char buffer[MAX_PATH]; - /* give up looking for a directory after we've had four - times as many tries as there are directories. */ - unsigned long allowed_tries = folder_count * 4; - int i; - srand(current_tick); - *(tc->dirfilter) = SHOW_MUSIC; - tc->sort_dir = global_settings.sort_dir; - while (!exit && allowed_tries--) - { - i = rand() % folder_count; - lseek(fd, sizeof(int) + (MAX_PATH * i), SEEK_SET); - read(fd, buffer, MAX_PATH); - /* is the current dir within our base dir and has music? */ - if ((base_len == 0 || !strncmp(buffer, dir, base_len)) - && check_subdir_for_music(buffer, "", false) == 0) - exit = true; - } - close(fd); - *(tc->dirfilter) = saved_dirfilter; - tc->sort_dir = global_settings.sort_dir; - reload_directory(); - if (exit) - { - strcpy(dir,buffer); - return 0; - } - } - else - close(fd); - } - } - - /* if the current file is within our base dir, use its dir instead */ - if (base_len == 0 || !strncmp(playlist->filename, dir, base_len)) - strmemccpy(dir, playlist->filename, playlist->dirlen); - - /* use the tree browser dircache to load files */ - *(tc->dirfilter) = SHOW_ALL; - - /* set up sorting/direction */ - tc->sort_dir = global_settings.sort_dir; - if (!is_forward) - { - static const char sortpairs[] = - { - [SORT_ALPHA] = SORT_ALPHA_REVERSED, - [SORT_DATE] = SORT_DATE_REVERSED, - [SORT_TYPE] = SORT_TYPE_REVERSED, - [SORT_ALPHA_REVERSED] = SORT_ALPHA, - [SORT_DATE_REVERSED] = SORT_DATE, - [SORT_TYPE_REVERSED] = SORT_TYPE, - }; - - if ((unsigned)tc->sort_dir < sizeof(sortpairs)) - tc->sort_dir = sortpairs[tc->sort_dir]; - } - - while (!exit) - { - struct entry *files; - int num_files = 0; - int i; - - if (ft_load(tc, (dir[0]=='\0')?"/":dir) < 0) - { - exit = true; - result = -1; - break; - } - - tree_lock_cache(tc); - files = tree_get_entries(tc); - num_files = tc->filesindir; - - for (i=0; i<num_files; i++) - { - /* user abort */ - if (action_userabort(TIMEOUT_NOBLOCK)) - { - result = -1; - exit = true; - break; - } - - if (files[i].attr & ATTR_DIRECTORY) - { - if (!start_dir) - { - result = check_subdir_for_music(dir, files[i].name, true); - if (result != -1) - { - exit = true; - break; - } - } - else if (!strcmp(start_dir, files[i].name)) - start_dir = NULL; - } - } - tree_unlock_cache(tc); - - if (!exit) - { - /* we've already descended to the base dir with nothing found, - check whether that contains music */ - if (strlen(dir) <= base_len) - { - result = check_subdir_for_music(dir, "", true); - if (result == -1) - /* there's no music files in the base directory, - treat as a fatal error */ - result = -2; - break; - } - else - { - /* move down to parent directory. current directory name is - stored as the starting point for the search in parent */ - start_dir = strrchr(dir, '/'); - if (start_dir) - { - *start_dir = '\0'; - start_dir++; - } - else - break; - } - } - } - - /* restore dirfilter */ - *(tc->dirfilter) = saved_dirfilter; - tc->sort_dir = global_settings.sort_dir; - - return result; -} - -/* - * Checks if there are any music files in the dir or any of its - * subdirectories. May be called recursively. - */ -static int check_subdir_for_music(char *dir, const char *subdir, bool recurse) -{ - int result = -1; - size_t dirlen = strlen(dir); - int num_files = 0; - int i; - struct entry *files; - bool has_music = false; - bool has_subdir = false; - struct tree_context* tc = tree_get_context(); - - if (path_append(dir + dirlen, PA_SEP_HARD, subdir, MAX_PATH - dirlen) >= - MAX_PATH - dirlen) - { - return 0; - } - - if (ft_load(tc, dir) < 0) - { - return -2; - } - - tree_lock_cache(tc); - files = tree_get_entries(tc); - num_files = tc->filesindir; - - for (i=0; i<num_files; i++) - { - if (files[i].attr & ATTR_DIRECTORY) - has_subdir = true; - else if ((files[i].attr & FILE_ATTR_MASK) == FILE_ATTR_AUDIO) - { - has_music = true; - break; - } - } - - if (has_music) - { - tree_unlock_cache(tc); - return 0; - } - - if (has_subdir && recurse) - { - for (i=0; i<num_files; i++) - { - if (action_userabort(TIMEOUT_NOBLOCK)) - { - result = -2; - break; - } - - if (files[i].attr & ATTR_DIRECTORY) - { - result = check_subdir_for_music(dir, files[i].name, true); - if (!result) - break; - } - } - } - tree_unlock_cache(tc); - - if (result < 0) - { - if (dirlen) - { - dir[dirlen] = '\0'; - } - else - { - strcpy(dir, PATH_ROOTSTR); - } - - /* we now need to reload our current directory */ - if(ft_load(tc, dir) < 0) - splash(HZ*2, ID2P(LANG_PLAYLIST_DIRECTORY_ACCESS_ERROR)); - } - return result; -} - -/* - * Returns absolute path of track - * - * dest: output buffer - * src: the file name from the playlist - * dir: the absolute path to the directory where the playlist resides - * - * The type of path in "src" determines what will be written to "dest": - * - * 1. UNIX-style absolute paths (/foo/bar) remain unaltered - * 2. Windows-style absolute paths (C:/foo/bar) will be converted into an - * absolute path by replacing the drive letter with the volume that the - * *playlist* resides on, ie. the volume in "dir" - * 3. Relative paths are converted to absolute paths by prepending "dir". - * This also applies to Windows-style relative paths "C:foo/bar" where - * the drive letter is accepted but ignored. - */ -static ssize_t format_track_path(char *dest, char *src, int buf_length, - const char *dir) -{ - size_t len = 0; - - /* Look for the end of the string */ - while (1) - { - int c = src[len]; - if (c == '\n' || c == '\r' || c == '\0') - break; - len++; - } - - /* Now work back killing white space */ - while (len > 0) - { - int c = src[len - 1]; - if (c != '\t' && c != ' ') - break; - len--; - } - - src[len] = '\0'; - - /* Replace backslashes with forward slashes */ - path_correct_separators(src, src); - - /* Handle Windows-style absolute paths */ - if (path_strip_drive(src, (const char **)&src, true) >= 0 && - src[-1] == PATH_SEPCH) - { - #ifdef HAVE_MULTIVOLUME - const char *p; - path_strip_last_volume(dir, &p, false); - dir = strmemdupa(dir, p - dir); /* empty if no volspec on dir */ - #else - dir = ""; /* only volume is root */ - #endif - } - - len = path_append(dest, *dir ? dir : PATH_ROOTSTR, src, buf_length); - if (len >= (size_t)buf_length) - return -1; /* buffer too small */ - - path_remove_dot_segments (dest, dest); - - return strlen (dest); -} - -/* - * Display splash message showing progress of playlist/directory insertion or - * save. - */ -static void display_playlist_count(int count, const unsigned char *fmt, - bool final) -{ - static long talked_tick = 0; - long id = P2ID(fmt); - if(global_settings.talk_menu && id>=0) - { - if(final || (count && (talked_tick == 0 - || TIME_AFTER(current_tick, talked_tick+5*HZ)))) - { - talked_tick = current_tick; - talk_number(count, false); - talk_id(id, true); - } - } - fmt = P2STR(fmt); - - splashf(0, fmt, count, str(LANG_OFF_ABORT)); -} - -/* - * Display buffer full message - */ -static void display_buffer_full(void) -{ - splash(HZ*2, ID2P(LANG_PLAYLIST_BUFFER_FULL)); -} - -/* - * Flush any cached control commands to disk. Called when playlist is being - * modified. Returns 0 on success and -1 on failure. - */ -static int flush_cached_control(struct playlist_info* playlist) -{ - int result = 0; - int i; - - if (!playlist->num_cached) - return 0; - - lseek(playlist->control_fd, 0, SEEK_END); - - for (i=0; i<playlist->num_cached; i++) - { - struct playlist_control_cache* cache = - &(playlist->control_cache[i]); - - switch (cache->command) - { - case PLAYLIST_COMMAND_PLAYLIST: - result = fdprintf(playlist->control_fd, "P:%d:%s:%s\n", - cache->i1, cache->s1, cache->s2); - break; - case PLAYLIST_COMMAND_ADD: - case PLAYLIST_COMMAND_QUEUE: - result = fdprintf(playlist->control_fd, "%c:%d:%d:", - (cache->command == PLAYLIST_COMMAND_ADD)?'A':'Q', - cache->i1, cache->i2); - if (result > 0) - { - /* save the position in file where name is written */ - int* seek_pos = (int *)cache->data; - *seek_pos = lseek(playlist->control_fd, 0, SEEK_CUR); - result = fdprintf(playlist->control_fd, "%s\n", - cache->s1); - } - break; - case PLAYLIST_COMMAND_DELETE: - result = fdprintf(playlist->control_fd, "D:%d\n", cache->i1); - break; - case PLAYLIST_COMMAND_SHUFFLE: - result = fdprintf(playlist->control_fd, "S:%d:%d\n", - cache->i1, cache->i2); - break; - case PLAYLIST_COMMAND_UNSHUFFLE: - result = fdprintf(playlist->control_fd, "U:%d\n", cache->i1); - break; - case PLAYLIST_COMMAND_RESET: - result = fdprintf(playlist->control_fd, "R\n"); - break; - default: - break; - } - - if (result <= 0) - break; - } - - if (result > 0) - { - playlist->num_cached = 0; - playlist->pending_control_sync = true; - - result = 0; - } - else - { - result = -1; - splash(HZ*2, ID2P(LANG_PLAYLIST_CONTROL_UPDATE_ERROR)); - } - - return result; -} - -/* - * Update control data with new command. Depending on the command, it may be - * cached or flushed to disk. - */ -static int update_control(struct playlist_info* playlist, - enum playlist_command command, int i1, int i2, - const char* s1, const char* s2, void* data) -{ - int result = 0; - struct playlist_control_cache* cache; - bool flush = false; - - mutex_lock(playlist->control_mutex); - - cache = &(playlist->control_cache[playlist->num_cached++]); - - cache->command = command; - cache->i1 = i1; - cache->i2 = i2; - cache->s1 = s1; - cache->s2 = s2; - cache->data = data; - - switch (command) - { - case PLAYLIST_COMMAND_PLAYLIST: - case PLAYLIST_COMMAND_ADD: - case PLAYLIST_COMMAND_QUEUE: -#ifndef HAVE_DIRCACHE - case PLAYLIST_COMMAND_DELETE: - case PLAYLIST_COMMAND_RESET: -#endif - flush = true; - break; - case PLAYLIST_COMMAND_SHUFFLE: - case PLAYLIST_COMMAND_UNSHUFFLE: - default: - /* only flush when needed */ - break; - } - - if (flush || playlist->num_cached == PLAYLIST_MAX_CACHE) - result = flush_cached_control(playlist); - - mutex_unlock(playlist->control_mutex); - - return result; -} - -/* - * sync control file to disk - */ -static void sync_control(struct playlist_info* playlist, bool force) -{ -#ifdef HAVE_DIRCACHE - if (playlist->started && force) -#else - (void) force; - - if (playlist->started) -#endif - { - if (playlist->pending_control_sync) - { - mutex_lock(playlist->control_mutex); - fsync(playlist->control_fd); - playlist->pending_control_sync = false; - mutex_unlock(playlist->control_mutex); - } - } -} - -/* - * Rotate indices such that first_index is index 0 - */ -static int rotate_index(const struct playlist_info* playlist, int index) -{ - index -= playlist->first_index; - if (index < 0) - index += playlist->amount; - - return index; -} - -/* * Allocate a temporary buffer for loading playlists */ static int alloc_tempbuf(size_t* buflen) @@ -2064,6 +1976,14 @@ static struct buflib_callbacks ops = { .move_callback = move_callback, .shrink_callback = NULL, }; + +/******************************************************************************/ +/******************************************************************************/ +/* ************************************************************************** */ +/* * PUBLIC INTERFACE FUNCTIONS * *********************************************/ +/* ************************************************************************** */ +/******************************************************************************/ +/******************************************************************************/ /* * Initialize playlist entries at startup */ @@ -2099,8 +2019,8 @@ void playlist_init(void) playlist->max_playlist_size * sizeof(*playlist->dcfrefs), &ops); playlist->dcfrefs = core_get_data(handle); copy_filerefs(playlist->dcfrefs, NULL, playlist->max_playlist_size); - create_thread(playlist_thread, playlist_stack, sizeof(playlist_stack), - 0, playlist_thread_name IF_PRIO(, PRIORITY_BACKGROUND) + create_thread(thread_playlist, playlist_stack, sizeof(playlist_stack), + 0, thread_playlist_name IF_PRIO(, PRIORITY_BACKGROUND) IF_COP(, CPU)); queue_init(&playlist_queue, true); @@ -2129,6 +2049,113 @@ void playlist_shutdown(void) } /* + * Add track to in_ram playlist. Used when playing directories. + */ +int playlist_add(const char *filename) +{ + struct playlist_info* playlist = ¤t_playlist; + int len = strlen(filename); + + if((len+1 > playlist->buffer_size - playlist->buffer_end_pos) || + (playlist->amount >= playlist->max_playlist_size)) + { + display_buffer_full(); + return -1; + } + + playlist->indices[playlist->amount] = playlist->buffer_end_pos; +#ifdef HAVE_DIRCACHE + copy_filerefs(&playlist->dcfrefs[playlist->amount], NULL, 1); +#endif + + playlist->amount++; + + strcpy((char*)&playlist->buffer[playlist->buffer_end_pos], filename); + playlist->buffer_end_pos += len; + playlist->buffer[playlist->buffer_end_pos++] = '\0'; + + return 0; +} + +/* returns number of tracks in playlist (includes queued/inserted tracks) */ +int playlist_amount_ex(const struct playlist_info* playlist) +{ + if (!playlist) + playlist = ¤t_playlist; + + return playlist->amount; +} + +/* returns number of tracks in current playlist */ +int playlist_amount(void) +{ + return playlist_amount_ex(NULL); +} + +/* + * Create a new playlist If playlist is not NULL then we're loading a + * playlist off disk for viewing/editing. The index_buffer is used to store + * playlist indices (required for and only used if !current playlist). The + * temp_buffer (if not NULL) is used as a scratchpad when loading indices. + */ +int playlist_create_ex(struct playlist_info* playlist, + const char* dir, const char* file, + void* index_buffer, int index_buffer_size, + void* temp_buffer, int temp_buffer_size) +{ + if (!playlist) + playlist = ¤t_playlist; + else + { + /* Initialize playlist structure */ + int r = rand() % 10; + playlist->current = false; + + /* Use random name for control file */ + snprintf(playlist->control_filename, sizeof(playlist->control_filename), + "%s.%d", PLAYLIST_CONTROL_FILE, r); + playlist->fd = -1; + playlist->control_fd = -1; + + if (index_buffer) + { + int num_indices = index_buffer_size / + playlist_get_required_bufsz(playlist, false, 1); + + if (num_indices > global_settings.max_files_in_playlist) + num_indices = global_settings.max_files_in_playlist; + + playlist->max_playlist_size = num_indices; + playlist->indices = index_buffer; +#ifdef HAVE_DIRCACHE + playlist->dcfrefs = (void *)&playlist->indices[num_indices]; +#endif + } + else + { + playlist->max_playlist_size = current_playlist.max_playlist_size; + playlist->indices = current_playlist.indices; +#ifdef HAVE_DIRCACHE + playlist->dcfrefs = current_playlist.dcfrefs; +#endif + } + + playlist->buffer_size = 0; + playlist->buffer_handle = -1; + playlist->buffer = NULL; + playlist->control_mutex = &created_playlist_mutex; + } + + new_playlist(playlist, dir, file); + + if (file) + /* load the playlist file */ + add_indices_to_playlist(playlist, temp_buffer, temp_buffer_size); + + return 0; +} + +/* * Create new playlist */ int playlist_create(const char *dir, const char *file) @@ -2162,474 +2189,196 @@ int playlist_create(const char *dir, const char *file) return 0; } -#define PLAYLIST_COMMAND_SIZE (MAX_PATH+12) - -/* - * Restore the playlist state based on control file commands. Called to - * resume playback after shutdown. - */ -int playlist_resume(void) +/* Returns false if 'steps' is out of bounds, else true */ +bool playlist_check(int steps) { struct playlist_info* playlist = ¤t_playlist; - char *buffer; - size_t buflen; - int handle; - int nread; - int total_read = 0; - int control_file_size = 0; - bool first = true; - bool sorted = true; - int result = -1; - splash(0, ID2P(LANG_WAIT)); - if (core_allocatable() < (1 << 10)) - talk_buffer_set_policy(TALK_BUFFER_LOOSE); /* back off voice buffer */ + /* always allow folder navigation */ + if (global_settings.next_folder && playlist->in_ram) + return true; -#ifdef HAVE_DIRCACHE - dircache_wait(); /* we need the dircache to use the files in the playlist */ -#endif + int index = get_next_index(playlist, steps, -1); - handle = alloc_tempbuf(&buflen); - if (handle < 0) - { - splashf(HZ * 2, "%s(): OOM", __func__); - return -1; - } + if (index < 0 && steps >= 0 && global_settings.repeat_mode == REPEAT_SHUFFLE) + index = get_next_index(playlist, steps, REPEAT_ALL); - /* align buffer for faster load times */ - buffer = core_get_data(handle); - STORAGE_ALIGN_BUFFER(buffer, buflen); - buflen = ALIGN_DOWN(buflen, 512); /* to avoid partial sector I/O */ + return (index >= 0); +} - playlist_shutdown(); /* flush any cached control commands to disk */ - empty_playlist(playlist, true); +/* + * Close files and delete control file for non-current playlist. + */ +void playlist_close(struct playlist_info* playlist) +{ + if (!playlist) + return; - playlist->control_fd = open(playlist->control_filename, O_RDWR); - if (playlist->control_fd < 0) - { - splash(HZ*2, ID2P(LANG_PLAYLIST_CONTROL_ACCESS_ERROR)); - goto out; + if (playlist->fd >= 0) { + close(playlist->fd); + playlist->fd = -1; } - playlist->control_created = true; - control_file_size = filesize(playlist->control_fd); - if (control_file_size <= 0) - { - splash(HZ*2, ID2P(LANG_PLAYLIST_CONTROL_ACCESS_ERROR)); - goto out; + if (playlist->control_fd >= 0) { + close(playlist->control_fd); + playlist->control_fd = -1; } - /* read a small amount first to get the header */ - nread = read(playlist->control_fd, buffer, - PLAYLIST_COMMAND_SIZE<buflen?PLAYLIST_COMMAND_SIZE:buflen); - if(nread <= 0) - { - splash(HZ*2, ID2P(LANG_PLAYLIST_CONTROL_ACCESS_ERROR)); - goto out; + if (playlist->control_created) { + remove(playlist->control_filename); + playlist->control_created = false; } +} - playlist->started = true; +/* + * Delete track at specified index. If index is PLAYLIST_DELETE_CURRENT then + * we want to delete the current playing track. + */ +int playlist_delete(struct playlist_info* playlist, int index) +{ + int result = 0; - while (1) + if (!playlist) + playlist = ¤t_playlist; + + if (check_control(playlist) < 0) { - result = 0; - int count; - enum playlist_command current_command = PLAYLIST_COMMAND_COMMENT; - int last_newline = 0; - int str_count = -1; - bool newline = true; - bool exit_loop = false; - char *p = buffer; - char *str1 = NULL; - char *str2 = NULL; - char *str3 = NULL; - unsigned long last_tick = current_tick; - splash_progress_set_delay(HZ / 2); /* wait 1/2 sec before progress */ - bool useraborted = false; - - for(count=0; count<nread && !exit_loop && !useraborted; count++,p++) - { - /* Show a splash while we are loading. */ - splash_progress((total_read + count), control_file_size, - "%s (%s)", str(LANG_WAIT), str(LANG_OFF_ABORT)); - if (TIME_AFTER(current_tick, last_tick + HZ/4)) - { - if (action_userabort(TIMEOUT_NOBLOCK)) - { - useraborted = true; - break; - } - last_tick = current_tick; - } - /* Are we on a new line? */ - if((*p == '\n') || (*p == '\r')) - { - *p = '\0'; + splash(HZ*2, ID2P(LANG_PLAYLIST_CONTROL_ACCESS_ERROR)); + return -1; + } - /* save last_newline in case we need to load more data */ - last_newline = count; + if (index == PLAYLIST_DELETE_CURRENT) + index = playlist->index; - switch (current_command) - { - case PLAYLIST_COMMAND_PLAYLIST: - { - /* str1=version str2=dir str3=file */ - int version; + result = remove_track_from_playlist(playlist, index, true); - if (!str1) - { - result = -2; - exit_loop = true; - break; - } - - if (!str2) - str2 = ""; - - if (!str3) - str3 = ""; - - version = atoi(str1); - - if (version != PLAYLIST_CONTROL_FILE_VERSION) - { - result = -3; - goto out; - } - - update_playlist_filename(playlist, str2, str3); - - if (str3[0] != '\0') - { - /* NOTE: add_indices_to_playlist() overwrites the - audiobuf so we need to reload control file - data */ - add_indices_to_playlist(playlist, buffer, buflen); - } - else if (str2[0] != '\0') - { - playlist->in_ram = true; - resume_directory(str2); - } - - /* load the rest of the data */ - first = false; - exit_loop = true; + if (result != -1 && (audio_status() & AUDIO_STATUS_PLAY) && + playlist->started) + audio_flush_and_reload_tracks(); - break; - } - case PLAYLIST_COMMAND_ADD: - case PLAYLIST_COMMAND_QUEUE: - { - /* str1=position str2=last_position str3=file */ - int position, last_position; - bool queue; - - if (!str1 || !str2 || !str3) - { - result = -4; - exit_loop = true; - break; - } - - position = atoi(str1); - last_position = atoi(str2); - - queue = (current_command == PLAYLIST_COMMAND_ADD)? - false:true; + return result; +} - /* seek position is based on str3's position in - buffer */ - if (add_track_to_playlist(playlist, str3, position, - queue, total_read+(str3-buffer)) < 0) - { - result = -5; - goto out; - } - - playlist->last_insert_pos = last_position; +/* + * Search specified directory for tracks and notify via callback. May be + * called recursively. + */ +int playlist_directory_tracksearch(const char* dirname, bool recurse, + int (*callback)(char*, void*), + void* context) +{ + char buf[MAX_PATH+1]; + int result = 0; + int num_files = 0; + int i;; + struct tree_context* tc = tree_get_context(); + struct tree_cache* cache = &tc->cache; + int old_dirfilter = *(tc->dirfilter); - break; - } - case PLAYLIST_COMMAND_DELETE: - { - /* str1=position */ - int position; - - if (!str1) - { - result = -6; - exit_loop = true; - break; - } - - position = atoi(str1); - - if (remove_track_from_playlist(playlist, position, - false) < 0) - { - result = -7; - goto out; - } + if (!callback) + return -1; - break; - } - case PLAYLIST_COMMAND_SHUFFLE: - { - /* str1=seed str2=first_index */ - int seed; - - if (!str1 || !str2) - { - result = -8; - exit_loop = true; - break; - } - - if (!sorted) - { - /* Always sort list before shuffling */ - sort_playlist(playlist, false, false); - } + /* use the tree browser dircache to load files */ + *(tc->dirfilter) = SHOW_ALL; - seed = atoi(str1); - playlist->first_index = atoi(str2); - - if (randomise_playlist(playlist, seed, false, - false) < 0) - { - result = -9; - goto out; - } - sorted = false; - break; - } - case PLAYLIST_COMMAND_UNSHUFFLE: - { - /* str1=first_index */ - if (!str1) - { - result = -10; - exit_loop = true; - break; - } - - playlist->first_index = atoi(str1); - - if (sort_playlist(playlist, false, false) < 0) - { - result = -11; - goto out; - } + if (ft_load(tc, dirname) < 0) + { + splash(HZ*2, ID2P(LANG_PLAYLIST_DIRECTORY_ACCESS_ERROR)); + *(tc->dirfilter) = old_dirfilter; + return -1; + } - sorted = true; - break; - } - case PLAYLIST_COMMAND_RESET: - { - playlist->last_insert_pos = -1; - break; - } - case PLAYLIST_COMMAND_COMMENT: - default: - break; - } + num_files = tc->filesindir; - newline = true; + /* we've overwritten the dircache so tree browser will need to be + reloaded */ + reload_directory(); - /* to ignore any extra newlines */ - current_command = PLAYLIST_COMMAND_COMMENT; - } - else if(newline) - { - newline = false; + for (i=0; i<num_files; i++) + { + /* user abort */ + if (action_userabort(TIMEOUT_NOBLOCK)) + { + result = -1; + break; + } - /* first non-comment line must always specify playlist */ - if (first && *p != 'P' && *p != '#') + struct entry *files = core_get_data(cache->entries_handle); + if (files[i].attr & ATTR_DIRECTORY) + { + if (recurse) + { + /* recursively add directories */ + if (path_append(buf, dirname, files[i].name, sizeof(buf)) + >= sizeof(buf)) { - result = -12; - exit_loop = true; - break; + continue; } - switch (*p) - { - case 'P': - /* playlist can only be specified once */ - if (!first) - { - result = -13; - exit_loop = true; - break; - } + result = playlist_directory_tracksearch(buf, recurse, + callback, context); + if (result < 0) + break; - current_command = PLAYLIST_COMMAND_PLAYLIST; - break; - case 'A': - current_command = PLAYLIST_COMMAND_ADD; - break; - case 'Q': - current_command = PLAYLIST_COMMAND_QUEUE; - break; - case 'D': - current_command = PLAYLIST_COMMAND_DELETE; - break; - case 'S': - current_command = PLAYLIST_COMMAND_SHUFFLE; - break; - case 'U': - current_command = PLAYLIST_COMMAND_UNSHUFFLE; - break; - case 'R': - current_command = PLAYLIST_COMMAND_RESET; - break; - case '#': - current_command = PLAYLIST_COMMAND_COMMENT; - break; - default: - result = -14; - exit_loop = true; - break; + /* we now need to reload our current directory */ + if(ft_load(tc, dirname) < 0) + { + result = -1; + break; } - str_count = -1; - str1 = NULL; - str2 = NULL; - str3 = NULL; - } - else if(current_command != PLAYLIST_COMMAND_COMMENT) - { - /* all control file strings are separated with a colon. - Replace the colon with 0 to get proper strings that can be - used by commands above */ - if (*p == ':') + num_files = tc->filesindir; + if (!num_files) { - *p = '\0'; - str_count++; - - if ((count+1) < nread) - { - switch (str_count) - { - case 0: - str1 = p+1; - break; - case 1: - str2 = p+1; - break; - case 2: - str3 = p+1; - break; - default: - /* allow last string to contain colons */ - *p = ':'; - break; - } - } + result = -1; + break; } } + else + continue; } - - if (result < 0) - { - splashf(HZ*2, "Err: %d, %s", result, str(LANG_PLAYLIST_CONTROL_INVALID)); - goto out; - } - - if (useraborted) - { - splash(HZ*2, ID2P(LANG_CANCEL)); - result = -1; - goto out; - } - if (!newline || (exit_loop && count<nread)) + else if ((files[i].attr & FILE_ATTR_MASK) == FILE_ATTR_AUDIO) { - if ((total_read + count) >= control_file_size) + if (path_append(buf, dirname, files[i].name, sizeof(buf)) + >= sizeof(buf)) { - /* no newline at end of control file */ - splashf(HZ*2, "Err: EOF, %s", str(LANG_PLAYLIST_CONTROL_INVALID)); - result = -15; - goto out; + continue; } - /* We didn't end on a newline or we exited loop prematurely. - Either way, re-read the remainder. */ - count = last_newline; - lseek(playlist->control_fd, total_read+count, SEEK_SET); - } - - total_read += count; - - if (first) - /* still looking for header */ - nread = read(playlist->control_fd, buffer, - PLAYLIST_COMMAND_SIZE<buflen?PLAYLIST_COMMAND_SIZE:buflen); - else - nread = read(playlist->control_fd, buffer, buflen); + if (callback(buf, context) != 0) + { + result = -1; + break; + } - /* Terminate on EOF */ - if(nread <= 0) - { - break; + /* let the other threads work */ + yield(); } } -#ifdef HAVE_DIRCACHE - queue_post(&playlist_queue, PLAYLIST_LOAD_POINTERS, 0); -#endif + /* restore dirfilter */ + *(tc->dirfilter) = old_dirfilter; -out: - talk_buffer_set_policy(TALK_BUFFER_DEFAULT); - core_free(handle); return result; } -/* - * Add track to in_ram playlist. Used when playing directories. - */ -int playlist_add(const char *filename) -{ - struct playlist_info* playlist = ¤t_playlist; - int len = strlen(filename); - - if((len+1 > playlist->buffer_size - playlist->buffer_end_pos) || - (playlist->amount >= playlist->max_playlist_size)) - { - display_buffer_full(); - return -1; - } - - playlist->indices[playlist->amount] = playlist->buffer_end_pos; -#ifdef HAVE_DIRCACHE - copy_filerefs(&playlist->dcfrefs[playlist->amount], NULL, 1); -#endif - - playlist->amount++; - - strcpy((char*)&playlist->buffer[playlist->buffer_end_pos], filename); - playlist->buffer_end_pos += len; - playlist->buffer[playlist->buffer_end_pos++] = '\0'; - return 0; +struct playlist_info *playlist_get_current(void) +{ + return ¤t_playlist; } -/* shuffle newly created playlist using random seed. */ -int playlist_shuffle(int random_seed, int start_index) +/* Returns index of current playing track for display purposes. This value + should not be used for resume purposes as it doesn't represent the actual + index into the playlist */ +int playlist_get_display_index(void) { struct playlist_info* playlist = ¤t_playlist; - bool start_current = false; - - if (start_index >= 0 && global_settings.play_selected) - { - /* store the seek position before the shuffle */ - playlist->index = playlist->first_index = start_index; - start_current = true; - } - - randomise_playlist(playlist, random_seed, start_current, true); + /* first_index should always be index 0 for display purposes */ + int index = rotate_index(playlist, playlist->index); - return playlist->index; + return (index+1); } /* returns the crc32 of the filename of the track at the specified index */ @@ -2650,212 +2399,48 @@ unsigned int playlist_get_filename_crc32(struct playlist_info *playlist, return crc_32(basename, strlen(basename), -1); } -/* resume a playlist track with the given crc_32 of the track name. */ -void playlist_resume_track(int start_index, unsigned int crc, - unsigned long elapsed, unsigned long offset) -{ - int i; - unsigned int tmp_crc; - struct playlist_info* playlist = ¤t_playlist; - tmp_crc = playlist_get_filename_crc32(playlist, start_index); - if (tmp_crc == crc) - { - playlist_start(start_index, elapsed, offset); - return; - } - - for (i = 0 ; i < playlist->amount; i++) - { - tmp_crc = playlist_get_filename_crc32(playlist, i); - if (tmp_crc == crc) - { - playlist_start(i, elapsed, offset); - return; - } - } - - /* If we got here the file wasnt found, so start from the beginning */ - playlist_start(0, 0, 0); -} - -/* start playing current playlist at specified index/offset */ -void playlist_start(int start_index, unsigned long elapsed, - unsigned long offset) -{ - struct playlist_info* playlist = ¤t_playlist; - - playlist->index = start_index; - - playlist->started = true; - sync_control(playlist, false); - audio_play(elapsed, offset); - audio_resume(); -} - -/* Returns false if 'steps' is out of bounds, else true */ -bool playlist_check(int steps) +/* returns index of first track in playlist */ +int playlist_get_first_index(const struct playlist_info* playlist) { - struct playlist_info* playlist = ¤t_playlist; - - /* always allow folder navigation */ - if (global_settings.next_folder && playlist->in_ram) - return true; - - int index = get_next_index(playlist, steps, -1); - - if (index < 0 && steps >= 0 && global_settings.repeat_mode == REPEAT_SHUFFLE) - index = get_next_index(playlist, steps, REPEAT_ALL); + if (!playlist) + playlist = ¤t_playlist; - return (index >= 0); + return playlist->first_index; } -/* get trackname of track that is "steps" away from current playing track. - NULL is used to identify end of playlist */ -const char* playlist_peek(int steps, char* buf, size_t buf_size) +/* returns the playlist filename */ +char *playlist_get_name(const struct playlist_info* playlist, char *buf, + int buf_size) { - struct playlist_info* playlist = ¤t_playlist; - int seek; - char *temp_ptr; - int index; - bool control_file; - - index = get_next_index(playlist, steps, -1); - if (index < 0) - return NULL; - - /* Just testing - don't care about the file name */ - if (!buf || !buf_size) - return ""; + if (!playlist) + playlist = ¤t_playlist; - control_file = playlist->indices[index] & PLAYLIST_INSERT_TYPE_MASK; - seek = playlist->indices[index] & PLAYLIST_SEEK_MASK; + strmemccpy(buf, playlist->filename, buf_size); - if (get_filename(playlist, index, seek, control_file, buf, - buf_size) < 0) + if (!buf[0]) return NULL; - temp_ptr = buf; - - if (!playlist->in_ram || control_file) - { - /* remove bogus dirs from beginning of path - (workaround for buggy playlist creation tools) */ - while (temp_ptr) - { - if (file_exists(temp_ptr)) - break; - - temp_ptr = strchr(temp_ptr+1, '/'); - } - - if (!temp_ptr) - { - /* Even though this is an invalid file, we still need to pass a - file name to the caller because NULL is used to indicate end - of playlist */ - return buf; - } - } - - return temp_ptr; + return buf; } -/* - * Update indices as track has changed - */ -int playlist_next(int steps) +/* return size of buffer needed for playlist to initialize num_indices entries */ +size_t playlist_get_required_bufsz(struct playlist_info* playlist, + bool include_namebuf, + int num_indices) { - struct playlist_info* playlist = ¤t_playlist; - int index; - - if ( (steps > 0) -#ifdef AB_REPEAT_ENABLE - && (global_settings.repeat_mode != REPEAT_AB) -#endif - && (global_settings.repeat_mode != REPEAT_ONE) ) - { - int i, j; - - /* We need to delete all the queued songs */ - for (i=0, j=steps; i<j; i++) - { - index = get_next_index(playlist, i, -1); - - if (index >= 0 && playlist->indices[index] & PLAYLIST_QUEUE_MASK) - { - remove_track_from_playlist(playlist, index, true); - steps--; /* one less track */ - } - } - } - - index = get_next_index(playlist, steps, -1); - - if (index < 0) - { - /* end of playlist... or is it */ - if (global_settings.repeat_mode == REPEAT_SHUFFLE && - playlist->amount > 1) - { - /* Repeat shuffle mode. Re-shuffle playlist and resume play */ - playlist->first_index = 0; - sort_playlist(playlist, false, false); - randomise_playlist(playlist, current_tick, false, true); - - playlist->started = true; - playlist->index = 0; - index = 0; - } - else if (playlist->in_ram && global_settings.next_folder) - { - index = create_and_play_dir(steps, true); - - if (index >= 0) - { - playlist->index = index; - } - } - - return index; - } - - playlist->index = index; - - if (playlist->last_insert_pos >= 0 && steps > 0) - { - /* check to see if we've gone beyond the last inserted track */ - int cur = rotate_index(playlist, index); - int last_pos = rotate_index(playlist, playlist->last_insert_pos); - - if (cur > last_pos) - { - /* reset last inserted track */ - playlist->last_insert_pos = -1; - - if (playlist->control_fd >= 0) - { - int result = update_control(playlist, PLAYLIST_COMMAND_RESET, - -1, -1, NULL, NULL, NULL); - - if (result < 0) - return result; - - sync_control(playlist, false); - } - } - } + size_t namebuf = 0; - return index; -} + if (!playlist) + playlist = ¤t_playlist; -/* try playing next or previous folder */ -bool playlist_next_dir(int direction) -{ - /* not to mess up real playlists */ - if(!current_playlist.in_ram) - return false; + size_t unit_size = sizeof (*playlist->indices); + #ifdef HAVE_DIRCACHE + unit_size += sizeof (*playlist->dcfrefs); + #endif + if (include_namebuf) + namebuf = AVERAGE_FILENAME_LENGTH * global_settings.max_files_in_dir; - return create_and_play_dir(direction, false) >= 0; + return (num_indices * unit_size) + namebuf; } /* Get resume info for current playing song. If return value is -1 then @@ -2869,256 +2454,54 @@ int playlist_get_resume_info(int *resume_index) return 0; } -/* Update resume info for current playing song. Returns -1 on error. */ -int playlist_update_resume_info(const struct mp3entry* id3) +/* returns shuffle seed of playlist */ +int playlist_get_seed(const struct playlist_info* playlist) { - struct playlist_info* playlist = ¤t_playlist; - - if (id3) - { - if (global_status.resume_index != playlist->index || - global_status.resume_elapsed != id3->elapsed || - global_status.resume_offset != id3->offset) - { - unsigned int crc = playlist_get_filename_crc32(playlist, - playlist->index); - global_status.resume_index = playlist->index; - global_status.resume_crc32 = crc; - global_status.resume_elapsed = id3->elapsed; - global_status.resume_offset = id3->offset; - status_save(); - } - } - else - { - global_status.resume_index = -1; - global_status.resume_crc32 = -1; - global_status.resume_elapsed = -1; - global_status.resume_offset = -1; - status_save(); - } + if (!playlist) + playlist = ¤t_playlist; - return 0; + return playlist->seed; } -/* Returns index of current playing track for display purposes. This value - should not be used for resume purposes as it doesn't represent the actual - index into the playlist */ -int playlist_get_display_index(void) +/* Fills info structure with information about track at specified index. + Returns 0 on success and -1 on failure */ +int playlist_get_track_info(struct playlist_info* playlist, int index, + struct playlist_track_info* info) { - struct playlist_info* playlist = ¤t_playlist; - - /* first_index should always be index 0 for display purposes */ - int index = rotate_index(playlist, playlist->index); - - return (index+1); -} + int seek; + bool control_file; -/* returns number of tracks in current playlist */ -int playlist_amount(void) -{ - return playlist_amount_ex(NULL); -} -/* set playlist->last_shuffle_start to playlist->amount for - PLAYLIST_INSERT_LAST_SHUFFLED command purposes*/ -void playlist_set_last_shuffled_start(void) -{ - struct playlist_info* playlist = ¤t_playlist; - playlist->last_shuffled_start = playlist->amount; -} -/* - * Create a new playlist If playlist is not NULL then we're loading a - * playlist off disk for viewing/editing. The index_buffer is used to store - * playlist indices (required for and only used if !current playlist). The - * temp_buffer (if not NULL) is used as a scratchpad when loading indices. - */ -int playlist_create_ex(struct playlist_info* playlist, - const char* dir, const char* file, - void* index_buffer, int index_buffer_size, - void* temp_buffer, int temp_buffer_size) -{ if (!playlist) playlist = ¤t_playlist; - else - { - /* Initialize playlist structure */ - int r = rand() % 10; - playlist->current = false; - - /* Use random name for control file */ - snprintf(playlist->control_filename, sizeof(playlist->control_filename), - "%s.%d", PLAYLIST_CONTROL_FILE, r); - playlist->fd = -1; - playlist->control_fd = -1; - - if (index_buffer) - { - int num_indices = index_buffer_size / - playlist_get_required_bufsz(playlist, false, 1); - - if (num_indices > global_settings.max_files_in_playlist) - num_indices = global_settings.max_files_in_playlist; - - playlist->max_playlist_size = num_indices; - playlist->indices = index_buffer; -#ifdef HAVE_DIRCACHE - playlist->dcfrefs = (void *)&playlist->indices[num_indices]; -#endif - } - else - { - playlist->max_playlist_size = current_playlist.max_playlist_size; - playlist->indices = current_playlist.indices; -#ifdef HAVE_DIRCACHE - playlist->dcfrefs = current_playlist.dcfrefs; -#endif - } - - playlist->buffer_size = 0; - playlist->buffer_handle = -1; - playlist->buffer = NULL; - playlist->control_mutex = &created_playlist_mutex; - } - - new_playlist(playlist, dir, file); - if (file) - /* load the playlist file */ - add_indices_to_playlist(playlist, temp_buffer, temp_buffer_size); - - return 0; -} - -/* - * Set the specified playlist as the current. - * NOTE: You will get undefined behaviour if something is already playing so - * remember to stop before calling this. Also, this call will - * effectively close your playlist, making it unusable. - */ -int playlist_set_current(struct playlist_info* playlist) -{ - if (!playlist || (check_control(playlist) < 0)) + if (index < 0 || index >= playlist->amount) return -1; - empty_playlist(¤t_playlist, false); - - strmemccpy(current_playlist.filename, playlist->filename, - sizeof(current_playlist.filename)); - - current_playlist.utf8 = playlist->utf8; - current_playlist.fd = playlist->fd; + control_file = playlist->indices[index] & PLAYLIST_INSERT_TYPE_MASK; + seek = playlist->indices[index] & PLAYLIST_SEEK_MASK; - close(playlist->control_fd); - playlist->control_fd = -1; - close(current_playlist.control_fd); - current_playlist.control_fd = -1; - remove(current_playlist.control_filename); - current_playlist.control_created = false; - if (rename(playlist->control_filename, - current_playlist.control_filename) < 0) - return -1; - current_playlist.control_fd = open(current_playlist.control_filename, - O_RDWR); - if (current_playlist.control_fd < 0) + if (get_filename(playlist, index, seek, control_file, info->filename, + sizeof(info->filename)) < 0) return -1; - current_playlist.control_created = true; - current_playlist.dirlen = playlist->dirlen; + info->attr = 0; - if (playlist->indices && playlist->indices != current_playlist.indices) + if (control_file) { - memcpy((void*)current_playlist.indices, (void*)playlist->indices, - playlist->max_playlist_size*sizeof(*playlist->indices)); -#ifdef HAVE_DIRCACHE - copy_filerefs(current_playlist.dcfrefs, playlist->dcfrefs, - playlist->max_playlist_size); -#endif - } - - current_playlist.first_index = playlist->first_index; - current_playlist.amount = playlist->amount; - current_playlist.last_insert_pos = playlist->last_insert_pos; - current_playlist.seed = playlist->seed; - current_playlist.shuffle_modified = playlist->shuffle_modified; - current_playlist.deleted = playlist->deleted; - current_playlist.num_inserted_tracks = playlist->num_inserted_tracks; - - memcpy(current_playlist.control_cache, playlist->control_cache, - sizeof(current_playlist.control_cache)); - current_playlist.num_cached = playlist->num_cached; - current_playlist.pending_control_sync = playlist->pending_control_sync; - - return 0; -} -struct playlist_info *playlist_get_current(void) -{ - return ¤t_playlist; -} -/* - * Close files and delete control file for non-current playlist. - */ -void playlist_close(struct playlist_info* playlist) -{ - if (!playlist) - return; - - if (playlist->fd >= 0) { - close(playlist->fd); - playlist->fd = -1; - } - - if (playlist->control_fd >= 0) { - close(playlist->control_fd); - playlist->control_fd = -1; - } - - if (playlist->control_created) { - remove(playlist->control_filename); - playlist->control_created = false; - } -} - -void playlist_sync(struct playlist_info* playlist) -{ - if (!playlist) - playlist = ¤t_playlist; - - sync_control(playlist, false); - if ((audio_status() & AUDIO_STATUS_PLAY) && playlist->started) - audio_flush_and_reload_tracks(); - -#ifdef HAVE_DIRCACHE - queue_post(&playlist_queue, PLAYLIST_LOAD_POINTERS, 0); -#endif -} - -/* - * Insert track into playlist at specified position (or one of the special - * positions). Returns position where track was inserted or -1 if error. - */ -int playlist_insert_track(struct playlist_info* playlist, const char *filename, - int position, bool queue, bool sync) -{ - int result; - - if (!playlist) - playlist = ¤t_playlist; + if (playlist->indices[index] & PLAYLIST_QUEUE_MASK) + info->attr |= PLAYLIST_ATTR_QUEUED; + else + info->attr |= PLAYLIST_ATTR_INSERTED; - if (check_control(playlist) < 0) - { - splash(HZ*2, ID2P(LANG_PLAYLIST_CONTROL_ACCESS_ERROR)); - return -1; } - result = add_track_to_playlist(playlist, filename, position, queue, -1); + if (playlist->indices[index] & PLAYLIST_SKIPPED) + info->attr |= PLAYLIST_ATTR_SKIPPED; - /* Check if we want manually sync later. For example when adding - * bunch of files from tagcache, syncing after every file wouldn't be - * a good thing to do. */ - if (sync && result >= 0) - playlist_sync(playlist); + info->index = index; + info->display_index = rotate_index(playlist, index) + 1; - return result; + return 0; } /* @@ -3160,7 +2543,7 @@ int playlist_insert_directory(struct playlist_info* playlist, context.position = position; context.queue = queue; context.count = 0; - + cpu_boost(true); result = playlist_directory_tracksearch(dirname, recurse, @@ -3243,11 +2626,11 @@ int playlist_insert_playlist(struct playlist_info* playlist, const char *filenam /* user abort */ if (action_userabort(TIMEOUT_NOBLOCK)) break; - + if (temp_buf[0] != '#' && temp_buf[0] != '\0') { int insert_pos; - + if (!utf8) { /* Use trackname as a temporay buffer. Note that trackname must @@ -3315,12 +2698,13 @@ int playlist_insert_playlist(struct playlist_info* playlist, const char *filenam } /* - * Delete track at specified index. If index is PLAYLIST_DELETE_CURRENT then - * we want to delete the current playing track. + * Insert track into playlist at specified position (or one of the special + * positions). Returns position where track was inserted or -1 if error. */ -int playlist_delete(struct playlist_info* playlist, int index) +int playlist_insert_track(struct playlist_info* playlist, const char *filename, + int position, bool queue, bool sync) { - int result = 0; + int result; if (!playlist) playlist = ¤t_playlist; @@ -3331,18 +2715,32 @@ int playlist_delete(struct playlist_info* playlist, int index) return -1; } - if (index == PLAYLIST_DELETE_CURRENT) - index = playlist->index; + result = add_track_to_playlist(playlist, filename, position, queue, -1); - result = remove_track_from_playlist(playlist, index, true); - - if (result != -1 && (audio_status() & AUDIO_STATUS_PLAY) && - playlist->started) - audio_flush_and_reload_tracks(); + /* Check if we want manually sync later. For example when adding + * bunch of files from tagcache, syncing after every file wouldn't be + * a good thing to do. */ + if (sync && result >= 0) + playlist_sync(playlist); return result; } + +/* returns true if playlist has been modified */ +bool playlist_modified(const struct playlist_info* playlist) +{ + if (!playlist) + playlist = ¤t_playlist; + + if (playlist->shuffle_modified || + playlist->deleted || + playlist->num_inserted_tracks > 0) + return true; + + return false; +} + /* * Move track at index to new_index. Tracks between the two are shifted * appropriately. Returns 0 on success and -1 on failure. @@ -3475,182 +2873,669 @@ int playlist_move(struct playlist_info* playlist, int index, int new_index) return result; } -/* shuffle currently playing playlist */ -int playlist_randomise(struct playlist_info* playlist, unsigned int seed, - bool start_current) +/* returns full path of playlist (minus extension) */ +char *playlist_name(const struct playlist_info* playlist, char *buf, + int buf_size) { - int result; + char *sep; if (!playlist) playlist = ¤t_playlist; - check_control(playlist); + strmemccpy(buf, playlist->filename+playlist->dirlen, buf_size); - result = randomise_playlist(playlist, seed, start_current, true); + if (!buf[0]) + return NULL; - if (result != -1 && (audio_status() & AUDIO_STATUS_PLAY) && - playlist->started) - audio_flush_and_reload_tracks(); + /* Remove extension */ + sep = strrchr(buf, '.'); + if (sep) + *sep = 0; - return result; + return buf; } -/* sort currently playing playlist */ -int playlist_sort(struct playlist_info* playlist, bool start_current) +/* + * Update indices as track has changed + */ +int playlist_next(int steps) { - int result; + struct playlist_info* playlist = ¤t_playlist; + int index; - if (!playlist) - playlist = ¤t_playlist; + if ( (steps > 0) +#ifdef AB_REPEAT_ENABLE + && (global_settings.repeat_mode != REPEAT_AB) +#endif + && (global_settings.repeat_mode != REPEAT_ONE) ) + { + int i, j; - check_control(playlist); + /* We need to delete all the queued songs */ + for (i=0, j=steps; i<j; i++) + { + index = get_next_index(playlist, i, -1); - result = sort_playlist(playlist, start_current, true); + if (index >= 0 && playlist->indices[index] & PLAYLIST_QUEUE_MASK) + { + remove_track_from_playlist(playlist, index, true); + steps--; /* one less track */ + } + } + } - if (result != -1 && (audio_status() & AUDIO_STATUS_PLAY) && - playlist->started) - audio_flush_and_reload_tracks(); + index = get_next_index(playlist, steps, -1); - return result; -} + if (index < 0) + { + /* end of playlist... or is it */ + if (global_settings.repeat_mode == REPEAT_SHUFFLE && + playlist->amount > 1) + { + /* Repeat shuffle mode. Re-shuffle playlist and resume play */ + playlist->first_index = 0; + sort_playlist(playlist, false, false); + randomise_playlist(playlist, current_tick, false, true); -/* returns true if playlist has been modified */ -bool playlist_modified(const struct playlist_info* playlist) -{ - if (!playlist) - playlist = ¤t_playlist; + playlist->started = true; + playlist->index = 0; + index = 0; + } + else if (playlist->in_ram && global_settings.next_folder) + { + index = create_and_play_dir(steps, true); - if (playlist->shuffle_modified || - playlist->deleted || - playlist->num_inserted_tracks > 0) - return true; + if (index >= 0) + { + playlist->index = index; + } + } - return false; -} + return index; + } -/* returns index of first track in playlist */ -int playlist_get_first_index(const struct playlist_info* playlist) -{ - if (!playlist) - playlist = ¤t_playlist; + playlist->index = index; - return playlist->first_index; -} + if (playlist->last_insert_pos >= 0 && steps > 0) + { + /* check to see if we've gone beyond the last inserted track */ + int cur = rotate_index(playlist, index); + int last_pos = rotate_index(playlist, playlist->last_insert_pos); -/* returns shuffle seed of playlist */ -int playlist_get_seed(const struct playlist_info* playlist) -{ - if (!playlist) - playlist = ¤t_playlist; + if (cur > last_pos) + { + /* reset last inserted track */ + playlist->last_insert_pos = -1; - return playlist->seed; + if (playlist->control_fd >= 0) + { + int result = update_control(playlist, PLAYLIST_COMMAND_RESET, + -1, -1, NULL, NULL, NULL); + + if (result < 0) + return result; + + sync_control(playlist, false); + } + } + } + + return index; } -/* returns number of tracks in playlist (includes queued/inserted tracks) */ -int playlist_amount_ex(const struct playlist_info* playlist) +/* try playing next or previous folder */ +bool playlist_next_dir(int direction) { - if (!playlist) - playlist = ¤t_playlist; + /* not to mess up real playlists */ + if(!current_playlist.in_ram) + return false; - return playlist->amount; + return create_and_play_dir(direction, false) >= 0; } -/* returns full path of playlist (minus extension) */ -char *playlist_name(const struct playlist_info* playlist, char *buf, - int buf_size) +/* get trackname of track that is "steps" away from current playing track. + NULL is used to identify end of playlist */ +const char* playlist_peek(int steps, char* buf, size_t buf_size) { - char *sep; + struct playlist_info* playlist = ¤t_playlist; + int seek; + char *temp_ptr; + int index; + bool control_file; - if (!playlist) - playlist = ¤t_playlist; + index = get_next_index(playlist, steps, -1); + if (index < 0) + return NULL; - strmemccpy(buf, playlist->filename+playlist->dirlen, buf_size); - - if (!buf[0]) + /* Just testing - don't care about the file name */ + if (!buf || !buf_size) + return ""; + + control_file = playlist->indices[index] & PLAYLIST_INSERT_TYPE_MASK; + seek = playlist->indices[index] & PLAYLIST_SEEK_MASK; + + if (get_filename(playlist, index, seek, control_file, buf, + buf_size) < 0) return NULL; - /* Remove extension */ - sep = strrchr(buf, '.'); - if (sep) - *sep = 0; + temp_ptr = buf; - return buf; + if (!playlist->in_ram || control_file) + { + /* remove bogus dirs from beginning of path + (workaround for buggy playlist creation tools) */ + while (temp_ptr) + { + if (file_exists(temp_ptr)) + break; + + temp_ptr = strchr(temp_ptr+1, '/'); + } + + if (!temp_ptr) + { + /* Even though this is an invalid file, we still need to pass a + file name to the caller because NULL is used to indicate end + of playlist */ + return buf; + } + } + + return temp_ptr; } -/* returns the playlist filename */ -char *playlist_get_name(const struct playlist_info* playlist, char *buf, - int buf_size) +/* shuffle currently playing playlist */ +int playlist_randomise(struct playlist_info* playlist, unsigned int seed, + bool start_current) { + int result; + if (!playlist) playlist = ¤t_playlist; - strmemccpy(buf, playlist->filename, buf_size); + check_control(playlist); - if (!buf[0]) - return NULL; + result = randomise_playlist(playlist, seed, start_current, true); - return buf; + if (result != -1 && (audio_status() & AUDIO_STATUS_PLAY) && + playlist->started) + audio_flush_and_reload_tracks(); + + return result; } -/* return size of buffer needed for playlist to initialize num_indices entries */ -size_t playlist_get_required_bufsz(struct playlist_info* playlist, - bool include_namebuf, - int num_indices) +/* + * Removes all tracks, from the playlist, leaving the presently playing + * track queued. + */ +int playlist_remove_all_tracks(struct playlist_info *playlist) { - size_t namebuf = 0; + int result; - if (!playlist) + if (playlist == NULL) playlist = ¤t_playlist; - size_t unit_size = sizeof (*playlist->indices); - #ifdef HAVE_DIRCACHE - unit_size += sizeof (*playlist->dcfrefs); - #endif - if (include_namebuf) - namebuf = AVERAGE_FILENAME_LENGTH * global_settings.max_files_in_dir; + while (playlist->index > 0) + if ((result = remove_track_from_playlist(playlist, 0, true)) < 0) + return result; - return (num_indices * unit_size) + namebuf; + while (playlist->amount > 1) + if ((result = remove_track_from_playlist(playlist, 1, true)) < 0) + return result; + + if (playlist->amount == 1) { + playlist->indices[0] |= PLAYLIST_QUEUED; + } + + return 0; } -/* Fills info structure with information about track at specified index. - Returns 0 on success and -1 on failure */ -int playlist_get_track_info(struct playlist_info* playlist, int index, - struct playlist_track_info* info) +/* + * Restore the playlist state based on control file commands. Called to + * resume playback after shutdown. + */ +int playlist_resume(void) { - int seek; - bool control_file; + struct playlist_info* playlist = ¤t_playlist; + char *buffer; + size_t buflen; + int handle; + int nread; + int total_read = 0; + int control_file_size = 0; + bool first = true; + bool sorted = true; + int result = -1; - if (!playlist) - playlist = ¤t_playlist; + splash(0, ID2P(LANG_WAIT)); + if (core_allocatable() < (1 << 10)) + talk_buffer_set_policy(TALK_BUFFER_LOOSE); /* back off voice buffer */ - if (index < 0 || index >= playlist->amount) +#ifdef HAVE_DIRCACHE + dircache_wait(); /* we need the dircache to use the files in the playlist */ +#endif + + handle = alloc_tempbuf(&buflen); + if (handle < 0) + { + splashf(HZ * 2, "%s(): OOM", __func__); return -1; + } - control_file = playlist->indices[index] & PLAYLIST_INSERT_TYPE_MASK; - seek = playlist->indices[index] & PLAYLIST_SEEK_MASK; + /* align buffer for faster load times */ + buffer = core_get_data(handle); + STORAGE_ALIGN_BUFFER(buffer, buflen); + buflen = ALIGN_DOWN(buflen, 512); /* to avoid partial sector I/O */ - if (get_filename(playlist, index, seek, control_file, info->filename, - sizeof(info->filename)) < 0) - return -1; + playlist_shutdown(); /* flush any cached control commands to disk */ + empty_playlist(playlist, true); - info->attr = 0; + playlist->control_fd = open(playlist->control_filename, O_RDWR); + if (playlist->control_fd < 0) + { + splash(HZ*2, ID2P(LANG_PLAYLIST_CONTROL_ACCESS_ERROR)); + goto out; + } + playlist->control_created = true; - if (control_file) + control_file_size = filesize(playlist->control_fd); + if (control_file_size <= 0) { - if (playlist->indices[index] & PLAYLIST_QUEUE_MASK) - info->attr |= PLAYLIST_ATTR_QUEUED; + splash(HZ*2, ID2P(LANG_PLAYLIST_CONTROL_ACCESS_ERROR)); + goto out; + } + + /* read a small amount first to get the header */ + nread = read(playlist->control_fd, buffer, + PLAYLIST_COMMAND_SIZE<buflen?PLAYLIST_COMMAND_SIZE:buflen); + if(nread <= 0) + { + splash(HZ*2, ID2P(LANG_PLAYLIST_CONTROL_ACCESS_ERROR)); + goto out; + } + + playlist->started = true; + + while (1) + { + result = 0; + int count; + enum playlist_command current_command = PLAYLIST_COMMAND_COMMENT; + int last_newline = 0; + int str_count = -1; + bool newline = true; + bool exit_loop = false; + char *p = buffer; + char *str1 = NULL; + char *str2 = NULL; + char *str3 = NULL; + unsigned long last_tick = current_tick; + splash_progress_set_delay(HZ / 2); /* wait 1/2 sec before progress */ + bool useraborted = false; + + for(count=0; count<nread && !exit_loop && !useraborted; count++,p++) + { + /* Show a splash while we are loading. */ + splash_progress((total_read + count), control_file_size, + "%s (%s)", str(LANG_WAIT), str(LANG_OFF_ABORT)); + if (TIME_AFTER(current_tick, last_tick + HZ/4)) + { + if (action_userabort(TIMEOUT_NOBLOCK)) + { + useraborted = true; + break; + } + last_tick = current_tick; + } + /* Are we on a new line? */ + if((*p == '\n') || (*p == '\r')) + { + *p = '\0'; + + /* save last_newline in case we need to load more data */ + last_newline = count; + + switch (current_command) + { + case PLAYLIST_COMMAND_PLAYLIST: + { + /* str1=version str2=dir str3=file */ + int version; + + if (!str1) + { + result = -2; + exit_loop = true; + break; + } + + if (!str2) + str2 = ""; + + if (!str3) + str3 = ""; + + version = atoi(str1); + + if (version != PLAYLIST_CONTROL_FILE_VERSION) + { + result = -3; + goto out; + } + + update_playlist_filename(playlist, str2, str3); + + if (str3[0] != '\0') + { + /* NOTE: add_indices_to_playlist() overwrites the + audiobuf so we need to reload control file + data */ + add_indices_to_playlist(playlist, buffer, buflen); + } + else if (str2[0] != '\0') + { + playlist->in_ram = true; + resume_directory(str2); + } + + /* load the rest of the data */ + first = false; + exit_loop = true; + + break; + } + case PLAYLIST_COMMAND_ADD: + case PLAYLIST_COMMAND_QUEUE: + { + /* str1=position str2=last_position str3=file */ + int position, last_position; + bool queue; + + if (!str1 || !str2 || !str3) + { + result = -4; + exit_loop = true; + break; + } + + position = atoi(str1); + last_position = atoi(str2); + + queue = (current_command == PLAYLIST_COMMAND_ADD)? + false:true; + + /* seek position is based on str3's position in + buffer */ + if (add_track_to_playlist(playlist, str3, position, + queue, total_read+(str3-buffer)) < 0) + { + result = -5; + goto out; + } + + playlist->last_insert_pos = last_position; + + break; + } + case PLAYLIST_COMMAND_DELETE: + { + /* str1=position */ + int position; + + if (!str1) + { + result = -6; + exit_loop = true; + break; + } + + position = atoi(str1); + + if (remove_track_from_playlist(playlist, position, + false) < 0) + { + result = -7; + goto out; + } + + break; + } + case PLAYLIST_COMMAND_SHUFFLE: + { + /* str1=seed str2=first_index */ + int seed; + + if (!str1 || !str2) + { + result = -8; + exit_loop = true; + break; + } + + if (!sorted) + { + /* Always sort list before shuffling */ + sort_playlist(playlist, false, false); + } + + seed = atoi(str1); + playlist->first_index = atoi(str2); + + if (randomise_playlist(playlist, seed, false, + false) < 0) + { + result = -9; + goto out; + } + sorted = false; + break; + } + case PLAYLIST_COMMAND_UNSHUFFLE: + { + /* str1=first_index */ + if (!str1) + { + result = -10; + exit_loop = true; + break; + } + + playlist->first_index = atoi(str1); + + if (sort_playlist(playlist, false, false) < 0) + { + result = -11; + goto out; + } + + sorted = true; + break; + } + case PLAYLIST_COMMAND_RESET: + { + playlist->last_insert_pos = -1; + break; + } + case PLAYLIST_COMMAND_COMMENT: + default: + break; + } + + newline = true; + + /* to ignore any extra newlines */ + current_command = PLAYLIST_COMMAND_COMMENT; + } + else if(newline) + { + newline = false; + + /* first non-comment line must always specify playlist */ + if (first && *p != 'P' && *p != '#') + { + result = -12; + exit_loop = true; + break; + } + + switch (*p) + { + case 'P': + /* playlist can only be specified once */ + if (!first) + { + result = -13; + exit_loop = true; + break; + } + + current_command = PLAYLIST_COMMAND_PLAYLIST; + break; + case 'A': + current_command = PLAYLIST_COMMAND_ADD; + break; + case 'Q': + current_command = PLAYLIST_COMMAND_QUEUE; + break; + case 'D': + current_command = PLAYLIST_COMMAND_DELETE; + break; + case 'S': + current_command = PLAYLIST_COMMAND_SHUFFLE; + break; + case 'U': + current_command = PLAYLIST_COMMAND_UNSHUFFLE; + break; + case 'R': + current_command = PLAYLIST_COMMAND_RESET; + break; + case '#': + current_command = PLAYLIST_COMMAND_COMMENT; + break; + default: + result = -14; + exit_loop = true; + break; + } + + str_count = -1; + str1 = NULL; + str2 = NULL; + str3 = NULL; + } + else if(current_command != PLAYLIST_COMMAND_COMMENT) + { + /* all control file strings are separated with a colon. + Replace the colon with 0 to get proper strings that can be + used by commands above */ + if (*p == ':') + { + *p = '\0'; + str_count++; + + if ((count+1) < nread) + { + switch (str_count) + { + case 0: + str1 = p+1; + break; + case 1: + str2 = p+1; + break; + case 2: + str3 = p+1; + break; + default: + /* allow last string to contain colons */ + *p = ':'; + break; + } + } + } + } + } + + if (result < 0) + { + splashf(HZ*2, "Err: %d, %s", result, str(LANG_PLAYLIST_CONTROL_INVALID)); + goto out; + } + + if (useraborted) + { + splash(HZ*2, ID2P(LANG_CANCEL)); + result = -1; + goto out; + } + if (!newline || (exit_loop && count<nread)) + { + if ((total_read + count) >= control_file_size) + { + /* no newline at end of control file */ + splashf(HZ*2, "Err: EOF, %s", str(LANG_PLAYLIST_CONTROL_INVALID)); + result = -15; + goto out; + } + + /* We didn't end on a newline or we exited loop prematurely. + Either way, re-read the remainder. */ + count = last_newline; + lseek(playlist->control_fd, total_read+count, SEEK_SET); + } + + total_read += count; + + if (first) + /* still looking for header */ + nread = read(playlist->control_fd, buffer, + PLAYLIST_COMMAND_SIZE<buflen?PLAYLIST_COMMAND_SIZE:buflen); else - info->attr |= PLAYLIST_ATTR_INSERTED; - + nread = read(playlist->control_fd, buffer, buflen); + + /* Terminate on EOF */ + if(nread <= 0) + { + break; + } } - if (playlist->indices[index] & PLAYLIST_SKIPPED) - info->attr |= PLAYLIST_ATTR_SKIPPED; - - info->index = index; - info->display_index = rotate_index(playlist, index) + 1; +#ifdef HAVE_DIRCACHE + queue_post(&playlist_queue, PLAYLIST_LOAD_POINTERS, 0); +#endif - return 0; +out: + talk_buffer_set_policy(TALK_BUFFER_DEFAULT); + core_free(handle); + return result; +} + +/* resume a playlist track with the given crc_32 of the track name. */ +void playlist_resume_track(int start_index, unsigned int crc, + unsigned long elapsed, unsigned long offset) +{ + int i; + unsigned int tmp_crc; + struct playlist_info* playlist = ¤t_playlist; + tmp_crc = playlist_get_filename_crc32(playlist, start_index); + if (tmp_crc == crc) + { + playlist_start(start_index, elapsed, offset); + return; + } + + for (i = 0 ; i < playlist->amount; i++) + { + tmp_crc = playlist_get_filename_crc32(playlist, i); + if (tmp_crc == crc) + { + playlist_start(i, elapsed, offset); + return; + } + } + + /* If we got here the file wasnt found, so start from the beginning */ + playlist_start(0, 0, 0); } /* save the current dynamic playlist to specified file. The @@ -3838,104 +3723,194 @@ reset_old_buffer: } /* - * Search specified directory for tracks and notify via callback. May be - * called recursively. + * Set the specified playlist as the current. + * NOTE: You will get undefined behaviour if something is already playing so + * remember to stop before calling this. Also, this call will + * effectively close your playlist, making it unusable. */ -int playlist_directory_tracksearch(const char* dirname, bool recurse, - int (*callback)(char*, void*), - void* context) +int playlist_set_current(struct playlist_info* playlist) { - char buf[MAX_PATH+1]; - int result = 0; - int num_files = 0; - int i;; - struct tree_context* tc = tree_get_context(); - struct tree_cache* cache = &tc->cache; - int old_dirfilter = *(tc->dirfilter); + if (!playlist || (check_control(playlist) < 0)) + return -1; - if (!callback) + empty_playlist(¤t_playlist, false); + + strmemccpy(current_playlist.filename, playlist->filename, + sizeof(current_playlist.filename)); + + current_playlist.utf8 = playlist->utf8; + current_playlist.fd = playlist->fd; + + close(playlist->control_fd); + playlist->control_fd = -1; + close(current_playlist.control_fd); + current_playlist.control_fd = -1; + remove(current_playlist.control_filename); + current_playlist.control_created = false; + if (rename(playlist->control_filename, + current_playlist.control_filename) < 0) return -1; + current_playlist.control_fd = open(current_playlist.control_filename, + O_RDWR); + if (current_playlist.control_fd < 0) + return -1; + current_playlist.control_created = true; - /* use the tree browser dircache to load files */ - *(tc->dirfilter) = SHOW_ALL; + current_playlist.dirlen = playlist->dirlen; - if (ft_load(tc, dirname) < 0) + if (playlist->indices && playlist->indices != current_playlist.indices) { - splash(HZ*2, ID2P(LANG_PLAYLIST_DIRECTORY_ACCESS_ERROR)); - *(tc->dirfilter) = old_dirfilter; - return -1; + memcpy((void*)current_playlist.indices, (void*)playlist->indices, + playlist->max_playlist_size*sizeof(*playlist->indices)); +#ifdef HAVE_DIRCACHE + copy_filerefs(current_playlist.dcfrefs, playlist->dcfrefs, + playlist->max_playlist_size); +#endif } - num_files = tc->filesindir; + current_playlist.first_index = playlist->first_index; + current_playlist.amount = playlist->amount; + current_playlist.last_insert_pos = playlist->last_insert_pos; + current_playlist.seed = playlist->seed; + current_playlist.shuffle_modified = playlist->shuffle_modified; + current_playlist.deleted = playlist->deleted; + current_playlist.num_inserted_tracks = playlist->num_inserted_tracks; - /* we've overwritten the dircache so tree browser will need to be - reloaded */ - reload_directory(); + memcpy(current_playlist.control_cache, playlist->control_cache, + sizeof(current_playlist.control_cache)); + current_playlist.num_cached = playlist->num_cached; + current_playlist.pending_control_sync = playlist->pending_control_sync; - for (i=0; i<num_files; i++) + return 0; +} + +/* set playlist->last_shuffle_start to playlist->amount for + PLAYLIST_INSERT_LAST_SHUFFLED command purposes*/ +void playlist_set_last_shuffled_start(void) +{ + struct playlist_info* playlist = ¤t_playlist; + playlist->last_shuffled_start = playlist->amount; +} + +/* shuffle newly created playlist using random seed. */ +int playlist_shuffle(int random_seed, int start_index) +{ + struct playlist_info* playlist = ¤t_playlist; + + bool start_current = false; + + if (start_index >= 0 && global_settings.play_selected) { - /* user abort */ - if (action_userabort(TIMEOUT_NOBLOCK)) - { - result = -1; - break; - } + /* store the seek position before the shuffle */ + playlist->index = playlist->first_index = start_index; + start_current = true; + } - struct entry *files = core_get_data(cache->entries_handle); - if (files[i].attr & ATTR_DIRECTORY) - { - if (recurse) - { - /* recursively add directories */ - if (path_append(buf, dirname, files[i].name, sizeof(buf)) - >= sizeof(buf)) - { - continue; - } + randomise_playlist(playlist, random_seed, start_current, true); - result = playlist_directory_tracksearch(buf, recurse, - callback, context); - if (result < 0) - break; + return playlist->index; +} - /* we now need to reload our current directory */ - if(ft_load(tc, dirname) < 0) - { - result = -1; - break; - } +/* Marks the index of the track to be skipped that is "steps" away from + * current playing track. + */ +void playlist_skip_entry(struct playlist_info *playlist, int steps) +{ + int index; - num_files = tc->filesindir; - if (!num_files) - { - result = -1; - break; - } - } - else - continue; - } - else if ((files[i].attr & FILE_ATTR_MASK) == FILE_ATTR_AUDIO) - { - if (path_append(buf, dirname, files[i].name, sizeof(buf)) - >= sizeof(buf)) - { - continue; - } + if (playlist == NULL) + playlist = ¤t_playlist; - if (callback(buf, context) != 0) - { - result = -1; - break; - } + /* need to account for already skipped tracks */ + steps = calculate_step_count(playlist, steps); - /* let the other threads work */ - yield(); - } - } + index = playlist->index + steps; + if (index < 0) + index += playlist->amount; + else if (index >= playlist->amount) + index -= playlist->amount; - /* restore dirfilter */ - *(tc->dirfilter) = old_dirfilter; + playlist->indices[index] |= PLAYLIST_SKIPPED; +} + +/* sort currently playing playlist */ +int playlist_sort(struct playlist_info* playlist, bool start_current) +{ + int result; + + if (!playlist) + playlist = ¤t_playlist; + + check_control(playlist); + + result = sort_playlist(playlist, start_current, true); + + if (result != -1 && (audio_status() & AUDIO_STATUS_PLAY) && + playlist->started) + audio_flush_and_reload_tracks(); return result; } + +/* start playing current playlist at specified index/offset */ +void playlist_start(int start_index, unsigned long elapsed, + unsigned long offset) +{ + struct playlist_info* playlist = ¤t_playlist; + + playlist->index = start_index; + + playlist->started = true; + sync_control(playlist, false); + audio_play(elapsed, offset); + audio_resume(); +} + +void playlist_sync(struct playlist_info* playlist) +{ + if (!playlist) + playlist = ¤t_playlist; + + sync_control(playlist, false); + if ((audio_status() & AUDIO_STATUS_PLAY) && playlist->started) + audio_flush_and_reload_tracks(); + +#ifdef HAVE_DIRCACHE + queue_post(&playlist_queue, PLAYLIST_LOAD_POINTERS, 0); +#endif +} + +/* Update resume info for current playing song. Returns -1 on error. */ +int playlist_update_resume_info(const struct mp3entry* id3) +{ + struct playlist_info* playlist = ¤t_playlist; + + if (id3) + { + if (global_status.resume_index != playlist->index || + global_status.resume_elapsed != id3->elapsed || + global_status.resume_offset != id3->offset) + { + unsigned int crc = playlist_get_filename_crc32(playlist, + playlist->index); + global_status.resume_index = playlist->index; + global_status.resume_crc32 = crc; + global_status.resume_elapsed = id3->elapsed; + global_status.resume_offset = id3->offset; + status_save(); + } + } + else + { + global_status.resume_index = -1; + global_status.resume_crc32 = -1; + global_status.resume_elapsed = -1; + global_status.resume_offset = -1; + status_save(); + } + + return 0; +} + + + diff --git a/apps/playlist.h b/apps/playlist.h index 2eca7355e4..0ecc7ccf77 100644 --- a/apps/playlist.h +++ b/apps/playlist.h @@ -74,22 +74,16 @@ struct playlist_control_cache { struct playlist_info { bool current; /* current playing playlist */ - char filename[MAX_PATH]; /* path name of m3u playlist on disk */ - char control_filename[MAX_PATH]; /* full path of control file */ bool utf8; /* playlist is in .m3u8 format */ + bool control_created; /* has control file been created? */ + bool in_ram; /* playlist stored in ram (dirplay) */ int fd; /* descriptor of the open playlist file */ int control_fd; /* descriptor of the open control file */ - bool control_created; /* has control file been created? */ - int dirlen; /* Length of the path to the playlist file */ - volatile unsigned long *indices; /* array of indices */ -#ifdef HAVE_DIRCACHE - struct dircache_fileref *dcfrefs; /* Dircache entry shortcuts */ -#endif int max_playlist_size; /* Max number of files in playlist. Mirror of global_settings.max_files_in_playlist */ - bool in_ram; /* playlist stored in ram (dirplay) */ + int num_inserted_tracks; /* number of tracks inserted */ + volatile unsigned long *indices; /* array of indices */ int buffer_handle; /* handle to the below buffer (-1 if non-buflib) */ - volatile char *buffer;/* buffer for in-ram playlists */ int buffer_size; /* size of buffer */ int buffer_end_pos; /* last position where buffer was written */ @@ -97,22 +91,25 @@ struct playlist_info int first_index; /* index of first song in playlist */ int amount; /* number of tracks in the index */ int last_insert_pos; /* last position we inserted a track */ - int seed; /* shuffle seed */ - bool shuffle_modified; /* has playlist been shuffled with - inserted tracks? */ bool deleted; /* have any tracks been deleted? */ - int num_inserted_tracks; /* number of tracks inserted */ bool started; /* has playlist been started? */ - + bool pending_control_sync; /* control file needs to be synced */ + bool shuffle_modified; /* has playlist been shuffled with + inserted tracks? */ + int last_shuffled_start; /* number of tracks when insert last + shuffled command start */ + int seed; /* shuffle seed */ /* cache of playlist control commands waiting to be flushed to to disk */ struct playlist_control_cache control_cache[PLAYLIST_MAX_CACHE]; int num_cached; /* number of cached entries */ - bool pending_control_sync; /* control file needs to be synced */ - - struct mutex *control_mutex; /* mutex for control file access */ - int last_shuffled_start; /* number of tracks when insert last - shuffled command start */ + struct mutex mutex; /* mutex for control file access */ +#ifdef HAVE_DIRCACHE + struct dircache_fileref *dcfrefs; /* Dircache entry shortcuts */ +#endif + int dirlen; /* Length of the path to the playlist file */ + char filename[MAX_PATH]; /* path name of m3u playlist on disk */ + char control_filename[MAX_PATH]; /* full path of control file */ }; struct playlist_track_info |