rockbox/apps/tree.c
Nils Wallménius b311367481 *** Lang v2 cleanup (FS#6574) ***
1) Introduces apps/features.txt that controls which strings are included 
for each target based on defines.
2) .lng and .voice files are now target specific and the format versions 
of both these file types have been bumped, which means that new voice 
files are needed. 
3) Use the 'features' mechanism to exclude strings for targets that 
didn't use them.
4) Delete unused and deprecated and duplicated strings, sort strings in 
english.lang

Some string IDs were changed so translations will be slightly worse than 
before.



git-svn-id: svn://svn.rockbox.org/rockbox/trunk@14198 a1c6a512-1295-4272-9138-f99709370657
2007-08-05 19:19:39 +00:00

1353 lines
38 KiB
C

/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
* $Id$
*
* Copyright (C) 2002 Daniel Stenberg
*
* All files in this archive are subject to the GNU General Public License.
* See the file COPYING in the source tree root for full license agreement.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
****************************************************************************/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdbool.h>
#include "applimits.h"
#include "dir.h"
#include "file.h"
#include "lcd.h"
#include "font.h"
#include "backlight.h"
#include "button.h"
#include "kernel.h"
#include "usb.h"
#include "tree.h"
#include "sprintf.h"
#include "audio.h"
#include "playlist.h"
#include "menu.h"
#include "gwps.h"
#include "settings.h"
#include "status.h"
#include "debug.h"
#include "ata.h"
#include "rolo.h"
#include "icons.h"
#include "lang.h"
#include "language.h"
#include "screens.h"
#include "keyboard.h"
#include "bookmark.h"
#include "onplay.h"
#include "buffer.h"
#include "plugin.h"
#include "power.h"
#include "action.h"
#include "talk.h"
#include "filetypes.h"
#include "misc.h"
#include "filetree.h"
#include "tagtree.h"
#ifdef HAVE_RECORDING
#include "recorder/recording.h"
#endif
#include "rtc.h"
#include "dircache.h"
#ifdef HAVE_TAGCACHE
#include "tagcache.h"
#endif
#include "yesno.h"
#include "gwps-common.h"
#include "eeprom_settings.h"
#include "scrobbler.h"
/* gui api */
#include "list.h"
#include "statusbar.h"
#include "splash.h"
#include "buttonbar.h"
#include "textarea.h"
#include "action.h"
#include "root_menu.h"
#if (LCD_DEPTH > 1) || (defined(HAVE_LCD_REMOTE) && (LCD_REMOTE_DEPTH > 1))
#include "backdrop.h"
#endif
static const struct filetype *filetypes;
static int filetypes_count;
struct gui_synclist tree_lists;
/* I put it here because other files doesn't use it yet,
* but should be elsewhere since it will be used mostly everywhere */
#ifdef HAS_BUTTONBAR
struct gui_buttonbar tree_buttonbar;
#endif
static struct tree_context tc;
bool boot_changed = false;
char lastfile[MAX_PATH];
static char lastdir[MAX_PATH];
#ifdef HAVE_TAGCACHE
static int lasttable, lastextra, lastfirstpos;
#endif
static int max_files = 0;
static bool reload_dir = false;
static bool start_wps = false;
static int curr_context = false;/* id3db or tree*/
static int dirbrowse(void);
static int ft_play_filenumber(int pos, int attr);
static int ft_play_dirname(char* name);
static void ft_play_filename(char *dir, char *file);
/*
* removes the extension of filename (if it doesn't start with a .)
* puts the result in buffer
*/
static char * strip_extension(char * filename, char * buffer)
{
int dotpos;
char * dot=strrchr(filename, '.');
if(dot!=0 && filename[0]!='.')
{
dotpos = dot-filename;
strncpy(buffer, filename, dotpos);
buffer[dotpos]='\0';
return(buffer);
}
else
return(filename);
}
static char * tree_get_filename(int selected_item, void * data, char *buffer)
{
struct tree_context * local_tc=(struct tree_context *)data;
char *name;
int attr=0;
#ifdef HAVE_TAGCACHE
bool id3db = *(local_tc->dirfilter) == SHOW_ID3DB;
if (id3db)
{
return tagtree_get_entry(&tc, selected_item)->name;
}
else
#endif
{
struct entry* dc = local_tc->dircache;
struct entry* e = &dc[selected_item];
name = e->name;
attr = e->attr;
}
/* if any file filter is on, and if it's not a directory,
* strip the extension */
if ( (*(local_tc->dirfilter) != SHOW_ID3DB) && !(attr & ATTR_DIRECTORY)
&& (*(local_tc->dirfilter) != SHOW_ALL) )
{
return(strip_extension(name, buffer));
}
return(name);
}
#ifdef HAVE_LCD_COLOR
static int tree_get_filecolor(int selected_item, void * data)
{
if (*tc.dirfilter == SHOW_ID3DB)
return -1;
struct tree_context * local_tc=(struct tree_context *)data;
struct entry* dc = local_tc->dircache;
struct entry* e = &dc[selected_item];
return filetype_get_color(e->name, e->attr);
}
#endif
static int tree_get_fileicon(int selected_item, void * data)
{
struct tree_context * local_tc=(struct tree_context *)data;
#ifdef HAVE_TAGCACHE
bool id3db = *(local_tc->dirfilter) == SHOW_ID3DB;
if (id3db) {
return tagtree_get_icon(&tc);
}
else
#endif
{
struct entry* dc = local_tc->dircache;
struct entry* e = &dc[selected_item];
return filetype_get_icon(e->attr);
}
}
bool check_rockboxdir(void)
{
DIR *dir = opendir(ROCKBOX_DIR);
if(!dir)
{ /* No need to localise this message.
If .rockbox is missing, it wouldn't work anyway */
int i;
FOR_NB_SCREENS(i)
screens[i].clear_display();
gui_syncsplash(HZ*2, "No .rockbox directory");
FOR_NB_SCREENS(i)
screens[i].clear_display();
gui_syncsplash(HZ*2, "Installation incomplete");
return false;
}
closedir(dir);
return true;
}
/* do this really late in the init sequence */
void tree_gui_init(void)
{
gui_sync_wps_screen_init();
check_rockboxdir();
strcpy(tc.currdir, "/");
#ifdef HAVE_LCD_CHARCELLS
int i;
FOR_NB_SCREENS(i)
screens[i].double_height(false);
#endif
#ifdef HAS_BUTTONBAR
gui_buttonbar_init(&tree_buttonbar);
/* since archos only have one screen, no need to create more than that */
gui_buttonbar_set_display(&tree_buttonbar, &(screens[SCREEN_MAIN]) );
#endif
gui_synclist_init(&tree_lists, &tree_get_filename, &tc, false, 1);
gui_synclist_set_icon_callback(&tree_lists, &tree_get_fileicon);
#ifdef HAVE_LCD_COLOR
gui_list_set_color_callback(&tree_lists.gui_list[SCREEN_MAIN],
&tree_get_filecolor);
#endif
}
struct tree_context* tree_get_context(void)
{
return &tc;
}
/* talkbox hovering delay, to avoid immediate disk activity */
#define HOVER_DELAY (HZ/2)
/*
* Returns the position of a given file in the current directory
* returns -1 if not found
*/
static int tree_get_file_position(char * filename)
{
int i;
/* use lastfile to determine the selected item (default=0) */
for (i=0; i < tc.filesindir; i++)
{
struct entry* dc = tc.dircache;
struct entry* e = &dc[i];
if (!strcasecmp(e->name, filename))
return(i);
}
return(-1);/* no file can match, returns undefined */
}
/*
* Called when a new dir is loaded (for example when returning from other apps ...)
* also completely redraws the tree
*/
static int update_dir(void)
{
bool changed = false;
#ifdef HAVE_TAGCACHE
bool id3db = *tc.dirfilter == SHOW_ID3DB;
/* Checks for changes */
if (id3db) {
if (tc.currtable != lasttable ||
tc.currextra != lastextra ||
tc.firstpos != lastfirstpos ||
reload_dir)
{
if (tagtree_load(&tc) < 0)
return -1;
lasttable = tc.currtable;
lastextra = tc.currextra;
lastfirstpos = tc.firstpos;
changed = true;
}
}
else
#endif
{
/* if the tc.currdir has been changed, reload it ...*/
if (strncmp(tc.currdir, lastdir, sizeof(lastdir)) || reload_dir) {
if (ft_load(&tc, NULL) < 0)
return -1;
strcpy(lastdir, tc.currdir);
changed = true;
}
}
/* if selected item is undefined */
if (tc.selected_item == -1)
{
/* use lastfile to determine the selected item */
tc.selected_item = tree_get_file_position(lastfile);
/* If the file doesn't exists, select the first one (default) */
if(tc.selected_item < 0)
tc.selected_item = 0;
changed = true;
}
if (changed)
{
if(
#ifdef HAVE_TAGCACHE
!id3db &&
#endif
(tc.dirfull ||
tc.filesindir == global_settings.max_files_in_dir) )
{
gui_syncsplash(HZ, str(LANG_SHOWDIR_BUFFER_FULL));
}
}
#ifdef HAVE_TAGCACHE
if (id3db)
{
if (global_settings.show_path_in_browser == SHOW_PATH_FULL
|| global_settings.show_path_in_browser == SHOW_PATH_CURRENT)
{
gui_synclist_set_title(&tree_lists, tagtree_get_title(&tc),
filetype_get_icon(ATTR_DIRECTORY));
}
else
{
/* Must clear the title as the list is reused */
gui_synclist_set_title(&tree_lists, NULL, NOICON);
}
}
else
#endif
{
if (global_settings.show_path_in_browser == SHOW_PATH_FULL)
{
gui_synclist_set_title(&tree_lists, tc.currdir,
filetype_get_icon(ATTR_DIRECTORY));
}
else if (global_settings.show_path_in_browser == SHOW_PATH_CURRENT)
{
char *title = strrchr(tc.currdir, '/') + 1;
if (*title == '\0')
{
/* Display "Files" for the root dir */
gui_synclist_set_title(&tree_lists, str(LANG_DIR_BROWSER),
filetype_get_icon(ATTR_DIRECTORY));
}
else if(0 == strcasecmp(tc.currdir, PLUGIN_DIR))
{
/* Display "Plugins" for the rocks dir */
gui_synclist_set_title(&tree_lists, str(LANG_PLUGINS),
filetype_get_icon(ATTR_DIRECTORY));
}
else
gui_synclist_set_title(&tree_lists, title,
filetype_get_icon(ATTR_DIRECTORY));
}
else
{
/* Must clear the title as the list is reused */
gui_synclist_set_title(&tree_lists, NULL, NOICON);
}
}
gui_synclist_set_nb_items(&tree_lists, tc.filesindir);
gui_synclist_set_icon_callback(&tree_lists, tree_get_fileicon);
if( tc.selected_item >= tc.filesindir)
tc.selected_item=tc.filesindir-1;
gui_synclist_select_item(&tree_lists, tc.selected_item);
#ifdef HAS_BUTTONBAR
if (global_settings.buttonbar) {
if (*tc.dirfilter < NUM_FILTER_MODES)
gui_buttonbar_set(&tree_buttonbar, str(LANG_SYSFONT_DIRBROWSE_F1),
str(LANG_SYSFONT_DIRBROWSE_F2),
str(LANG_SYSFONT_DIRBROWSE_F3));
else
gui_buttonbar_set(&tree_buttonbar, "<<<", "", "");
gui_buttonbar_draw(&tree_buttonbar);
}
#endif
gui_synclist_draw(&tree_lists);
gui_syncstatusbar_draw(&statusbars, true);
return tc.filesindir;
}
/* load tracks from specified directory to resume play */
void resume_directory(const char *dir)
{
#ifdef HAVE_TAGCACHE
bool id3db = *tc.dirfilter == SHOW_ID3DB;
#endif
if (ft_load(&tc, dir) < 0)
return;
lastdir[0] = 0;
ft_build_playlist(&tc, 0);
#ifdef HAVE_TAGCACHE
if (id3db)
tagtree_load(&tc);
#endif
}
/* Returns the current working directory and also writes cwd to buf if
non-NULL. In case of error, returns NULL. */
char *getcwd(char *buf, int size)
{
if (!buf)
return tc.currdir;
else if (size > 0)
{
strncpy(buf, tc.currdir, size);
return buf;
}
else
return NULL;
}
/* Force a reload of the directory next time directory browser is called */
void reload_directory(void)
{
reload_dir = true;
}
void get_current_file(char* buffer, int buffer_len)
{
#ifdef HAVE_TAGCACHE
/* in ID3DB mode it is a bad idea to call this function */
/* (only happens with `follow playlist') */
if( *tc.dirfilter == SHOW_ID3DB )
return;
#endif
struct entry* dc = tc.dircache;
struct entry* e = &dc[tc.selected_item];
snprintf(buffer, buffer_len, "%s/%s", getcwd(NULL,0),
tc.dirlength ? e->name : "");
}
/* Allow apps to change our dirfilter directly (required for sub browsers)
if they're suddenly going to become a file browser for example */
void set_dirfilter(int l_dirfilter)
{
*tc.dirfilter = l_dirfilter;
}
/* Selects a file and update tree context properly */
void set_current_file(char *path)
{
char *name;
int i;
#ifdef HAVE_TAGCACHE
/* in ID3DB mode it is a bad idea to call this function */
/* (only happens with `follow playlist') */
if( *tc.dirfilter == SHOW_ID3DB )
return;
#endif
/* separate directory from filename */
/* gets the directory's name and put it into tc.currdir */
name = strrchr(path+1,'/');
if (name)
{
*name = 0;
strcpy(tc.currdir, path);
*name = '/';
name++;
}
else
{
strcpy(tc.currdir, "/");
name = path+1;
}
strcpy(lastfile, name);
/* If we changed dir we must recalculate the dirlevel
and adjust the selected history properly */
if (strncmp(tc.currdir,lastdir,sizeof(lastdir)))
{
tc.dirlevel = 0;
tc.selected_item_history[tc.dirlevel] = -1;
/* use '/' to calculate dirlevel */
for (i = 1; path[i] != '\0'; i++)
{
if (path[i] == '/')
{
tc.dirlevel++;
tc.selected_item_history[tc.dirlevel] = -1;
}
}
}
if (ft_load(&tc, NULL) >= 0)
{
tc.selected_item = tree_get_file_position(lastfile);
}
}
/* main loop, handles key events */
static int dirbrowse()
{
int numentries=0;
char buf[MAX_PATH];
int lasti = -1;
unsigned button, returned_button;
bool reload_root = false;
int lastfilter = *tc.dirfilter;
bool lastsortcase = global_settings.sort_case;
bool need_update = true;
bool exit_func = false;
long thumbnail_time = -1; /* for delaying a thumbnail */
char* currdir = tc.currdir; /* just a shortcut */
#ifdef HAVE_TAGCACHE
bool id3db = *tc.dirfilter == SHOW_ID3DB;
if (id3db)
curr_context=CONTEXT_ID3DB;
else
#endif
curr_context=CONTEXT_TREE;
if (tc.selected_item < 0)
tc.selected_item = 0;
#ifdef HAVE_TAGCACHE
tc.firstpos=0;
lasttable = -1;
lastextra = -1;
lastfirstpos = 0;
#endif
start_wps = false;
numentries = update_dir();
if (numentries == -1)
return false; /* currdir is not a directory */
if (*tc.dirfilter > NUM_FILTER_MODES && numentries==0)
{
gui_syncsplash(HZ*2, str(LANG_NO_FILES));
return false; /* No files found for rockbox_browser() */
}
while(1) {
struct entry *dircache = tc.dircache;
bool restore = false;
if (tc.dirlevel < 0)
tc.dirlevel = 0; /* shouldnt be needed.. this code needs work! */
#ifdef BOOTFILE
if (boot_changed) {
char *lines[]={str(LANG_BOOT_CHANGED), str(LANG_REBOOT_NOW)};
struct text_message message={lines, 2};
if(gui_syncyesno_run(&message, NULL, NULL)==YESNO_YES)
rolo_load("/" BOOTFILE);
restore = true;
boot_changed = false;
}
#endif
button = get_action(CONTEXT_TREE,HZ/5);
returned_button = gui_synclist_do_button(&tree_lists, button,LIST_WRAP_UNLESS_HELD);
if (returned_button)
need_update = true;
if (returned_button == ACTION_STD_CANCEL)
button = ACTION_STD_CANCEL;
tc.selected_item = gui_synclist_get_sel_pos(&tree_lists);
switch ( button ) {
case ACTION_STD_OK:
/* nothing to do if no files to display */
if ( numentries == 0 )
break;
#ifdef HAVE_TAGCACHE
switch (id3db?tagtree_enter(&tc):ft_enter(&tc))
#else
switch (ft_enter(&tc))
#endif
{
case 1: reload_dir = true; break;
case 2: start_wps = true; break;
case 3: exit_func = true; break;
default: break;
}
restore = true;
break;
case ACTION_STD_CANCEL:
if (*tc.dirfilter > NUM_FILTER_MODES && tc.dirlevel < 1) {
exit_func = true;
break;
}
if ((*tc.dirfilter == SHOW_ID3DB && tc.dirlevel == 0) ||
((*tc.dirfilter != SHOW_ID3DB && !strcmp(currdir,"/"))))
{
if (returned_button == ACTION_STD_CANCEL)
break;
else
return GO_TO_ROOT;
}
#ifdef HAVE_TAGCACHE
if (id3db)
tagtree_exit(&tc);
else
#endif
if (ft_exit(&tc) == 3)
exit_func = true;
restore = true;
break;
case ACTION_TREE_STOP:
if (list_stop_handler())
restore = true;
break;
case ACTION_STD_MENU:
return GO_TO_ROOT;
break;
#ifdef HAVE_RECORDING
case ACTION_STD_REC:
return GO_TO_RECSCREEN;
#endif
case ACTION_TREE_WPS:
return GO_TO_PREVIOUS_MUSIC;
break;
#ifdef HAVE_QUICKSCREEN
case ACTION_STD_QUICKSCREEN:
/* don't enter f2 from plugin browser */
if (*tc.dirfilter < NUM_FILTER_MODES)
{
if (quick_screen_quick(button))
reload_dir = true;
restore = true;
}
break;
#endif
#ifdef BUTTON_F3
case ACTION_F3:
/* don't enter f3 from plugin browser */
if (*tc.dirfilter < NUM_FILTER_MODES)
{
if (quick_screen_f3(BUTTON_F3))
reload_dir = true;
restore = true;
}
break;
#endif
case ACTION_STD_CONTEXT:
{
int onplay_result;
int attr = 0;
if(!numentries)
onplay_result = onplay(NULL, 0, curr_context);
else {
#ifdef HAVE_TAGCACHE
if (id3db)
{
if (tagtree_get_attr(&tc) == FILE_ATTR_AUDIO)
{
attr = FILE_ATTR_AUDIO;
tagtree_get_filename(&tc, buf, sizeof(buf));
}
else
attr = ATTR_DIRECTORY;
}
else
#endif
{
attr = dircache[tc.selected_item].attr;
if (currdir[1]) /* Not in / */
snprintf(buf, sizeof buf, "%s/%s",
currdir,
dircache[tc.selected_item].name);
else /* In / */
snprintf(buf, sizeof buf, "/%s",
dircache[tc.selected_item].name);
}
onplay_result = onplay(buf, attr, curr_context);
}
switch (onplay_result)
{
case ONPLAY_MAINMENU:
return GO_TO_ROOT;
case ONPLAY_OK:
restore = true;
break;
case ONPLAY_RELOAD_DIR:
reload_dir = true;
break;
case ONPLAY_START_PLAY:
return GO_TO_WPS;
break;
}
break;
}
case ACTION_NONE:
if (thumbnail_time != -1 &&
TIME_AFTER(current_tick, thumbnail_time))
{ /* a delayed hovering thumbnail is due now */
int res;
int attr;
char* name;
#ifdef HAVE_TAGCACHE
if (id3db)
{
attr = tagtree_get_attr(&tc);
name = tagtree_get_entry(&tc, lasti)->name;
}
else
#endif
{
attr = dircache[lasti].attr;
name = dircache[lasti].name;
}
if (attr & ATTR_DIRECTORY)
{
DEBUGF("Playing directory thumbnail: %s", currdir);
res = ft_play_dirname(name);
if (res < 0) /* failed, not existing */
{
/* say the number or spell if required as a fallback */
switch (global_settings.talk_dir)
{
case 1: /* dirs as numbers */
talk_id(VOICE_DIR, false);
talk_number(lasti+1, true);
break;
case 2: /* dirs spelled */
talk_spell(name, false);
break;
}
}
}
else
{
DEBUGF("Playing file thumbnail: %s/%s%s\n",
currdir, name,
file_thumbnail_ext);
/* no fallback necessary, we knew in advance
that the file exists */
ft_play_filename(currdir, name);
}
thumbnail_time = -1; /* job done */
}
gui_syncstatusbar_draw(&statusbars, false);
break;
#ifdef HAVE_HOTSWAP
case SYS_FS_CHANGED:
#ifdef HAVE_TAGCACHE
if (!id3db)
#endif
reload_dir = true;
/* The 'dir no longer valid' situation will be caught later
* by checking the showdir() result. */
break;
#endif
default:
if (default_event_handler(button) == SYS_USB_CONNECTED)
{
if(*tc.dirfilter > NUM_FILTER_MODES)
/* leave sub-browsers after usb, doing otherwise
might be confusing to the user */
exit_func = true;
else
reload_dir = true;
}
break;
}
if (start_wps)
return GO_TO_WPS;
if ( button )
{
ata_spin();
}
check_rescan:
/* do we need to rescan dir? */
if (reload_dir || reload_root ||
lastfilter != *tc.dirfilter ||
lastsortcase != global_settings.sort_case)
{
if ( reload_root ) {
strcpy(currdir, "/");
tc.dirlevel = 0;
#ifdef HAVE_TAGCACHE
tc.currtable = 0;
tc.currextra = 0;
lasttable = -1;
lastextra = -1;
#endif
reload_root = false;
}
if (! reload_dir )
{
gui_synclist_select_item(&tree_lists, 0);
gui_synclist_draw(&tree_lists);
tc.selected_item = 0;
lastdir[0] = 0;
}
lastfilter = *tc.dirfilter;
lastsortcase = global_settings.sort_case;
restore = true;
}
if (exit_func)
return GO_TO_PREVIOUS;
if (restore || reload_dir) {
/* restore display */
numentries = update_dir();
if (currdir[1] && (numentries < 0))
{ /* not in root and reload failed */
reload_root = true; /* try root */
reload_dir = false;
goto check_rescan;
}
need_update = true;
reload_dir = false;
}
if(need_update) {
need_update=false;
if ( numentries > 0 ) {
/* Voice the file if changed */
if(lasti != tc.selected_item || restore) {
int attr;
char* name;
lasti = tc.selected_item;
thumbnail_time = -1; /* Cancel whatever we were
about to say */
#ifdef HAVE_TAGCACHE
if (id3db)
{
attr = tagtree_get_attr(&tc);
name = tagtree_get_entry(&tc, tc.selected_item)->name;
}
else
#endif
{
attr = dircache[tc.selected_item].attr;
name = dircache[tc.selected_item].name;
}
/* Directory? */
if (attr & ATTR_DIRECTORY)
{
/* schedule thumbnail playback if required */
if (global_settings.talk_dir_clip)
thumbnail_time = current_tick + HOVER_DELAY;
else
{
/* talk directly */
switch (global_settings.talk_dir)
{
case 1: /* dirs as numbers */
talk_id(VOICE_DIR, false);
talk_number(tc.selected_item+1, true);
break;
case 2: /* dirs spelled */
talk_spell(name, false);
break;
}
}
}
else /* file */
{
/* schedule thumbnail playback if required */
if (global_settings.talk_file_clip && (attr & FILE_ATTR_THUMBNAIL))
thumbnail_time = current_tick + HOVER_DELAY;
else
{
/* talk directly */
switch (global_settings.talk_file)
{
case 1: /* files as numbers */
ft_play_filenumber(
tc.selected_item-tc.dirsindir+1,
attr & FILE_ATTR_MASK);
break;
case 2: /* files spelled */
talk_spell(name, false);
break;
}
}
}
}
}
}
}
return true;
}
static int plsize = 0;
static long pltick;
static bool add_dir(char* dirname, int len, int fd)
{
bool abort = false;
DIR* dir;
/* check for user abort */
if (action_userabort(TIMEOUT_NOBLOCK))
return true;
dir = opendir(dirname);
if(!dir)
return true;
while (true) {
struct dirent *entry;
entry = readdir(dir);
if (!entry)
break;
if (entry->attribute & ATTR_DIRECTORY) {
int dirlen = strlen(dirname);
bool result;
if (!strcmp((char *)entry->d_name, ".") ||
!strcmp((char *)entry->d_name, ".."))
continue;
if (dirname[1])
snprintf(dirname+dirlen, len-dirlen, "/%s", entry->d_name);
else
snprintf(dirname, len, "/%s", entry->d_name);
result = add_dir(dirname, len, fd);
dirname[dirlen] = '\0';
if (result) {
abort = true;
break;
}
}
else {
int x = strlen((char *)entry->d_name);
int i;
char *cp = strrchr((char *)entry->d_name,'.');
if (cp) {
cp++;
/* add all supported audio files to playlists */
for (i=0; i < filetypes_count; i++) {
if (filetypes[i].tree_attr == FILE_ATTR_AUDIO) {
if (!strcasecmp(cp, filetypes[i].extension)) {
char buf[8];
int i;
write(fd, dirname, strlen(dirname));
write(fd, "/", 1);
write(fd, entry->d_name, x);
write(fd, "\n", 1);
plsize++;
if(TIME_AFTER(current_tick, pltick+HZ/4)) {
pltick = current_tick;
snprintf(buf, sizeof buf, "%d", plsize);
#ifdef HAVE_LCD_BITMAP
FOR_NB_SCREENS(i)
{
screens[i].puts(0, 4, (unsigned char *)buf);
gui_textarea_update(&screens[i]);
}
#else
x = 10;
if (plsize > 999)
x=7;
else {
if (plsize > 99)
x=8;
else {
if (plsize > 9)
x=9;
}
}
FOR_NB_SCREENS(i) {
screens[i].puts(x,0,buf);
}
#endif
}
break;
}
}
}
}
}
}
closedir(dir);
return abort;
}
bool create_playlist(void)
{
int fd;
int i;
char filename[MAX_PATH];
pltick = current_tick;
snprintf(filename, sizeof filename, "%s.m3u8",
tc.currdir[1] ? tc.currdir : "/root");
FOR_NB_SCREENS(i)
{
gui_textarea_clear(&screens[i]);
screens[i].puts(0, 0, str(LANG_CREATING));
screens[i].puts_scroll(0, 1, (unsigned char *)filename);
#if defined(HAVE_LCD_BITMAP) || defined(SIMULATOR)
gui_textarea_update(&screens[i]);
#endif
}
fd = creat(filename);
if (fd < 0)
return false;
trigger_cpu_boost();
snprintf(filename, sizeof(filename), "%s",
tc.currdir[1] ? tc.currdir : "/");
plsize = 0;
add_dir(filename, sizeof(filename), fd);
close(fd);
sleep(HZ);
return true;
}
int rockbox_browse(const char *root, int dirfilter)
{
int ret_val = 0;
int *last_filter = tc.dirfilter;
tc.dirfilter = &dirfilter;
reload_dir = true;
if (dirfilter >= NUM_FILTER_MODES)
{
static struct tree_context backup;
int last_context;
backup = tc;
tc.selected_item = 0;
tc.dirlevel = 0;
memcpy(tc.currdir, root, sizeof(tc.currdir));
start_wps = false;
last_context = curr_context;
ret_val = dirbrowse();
tc = backup;
curr_context = last_context;
}
else
{
static char buf[MAX_PATH];
if (dirfilter != SHOW_ID3DB)
tc.dirfilter = &global_settings.dirfilter;
strcpy(buf,root);
set_current_file(buf);
ret_val = dirbrowse();
}
tc.dirfilter = last_filter;
return ret_val;
}
void tree_mem_init(void)
{
/* We copy the settings value in case it is changed by the user. We can't
use it until the next reboot. */
max_files = global_settings.max_files_in_dir;
/* initialize tree context struct */
memset(&tc, 0, sizeof(tc));
tc.dirfilter = &global_settings.dirfilter;
tc.name_buffer_size = AVERAGE_FILENAME_LENGTH * max_files;
tc.name_buffer = buffer_alloc(tc.name_buffer_size);
tc.dircache_size = max_files * sizeof(struct entry);
tc.dircache = buffer_alloc(tc.dircache_size);
tree_get_filetypes(&filetypes, &filetypes_count);
}
void bookmark_play(char *resume_file, int index, int offset, int seed,
char *filename)
{
int i;
char* suffix = strrchr(resume_file, '.');
if (suffix != NULL &&
(!strcasecmp(suffix, ".m3u") || !strcasecmp(suffix, ".m3u8")))
{
/* Playlist playback */
char* slash;
/* check that the file exists */
int fd = open(resume_file, O_RDONLY);
if(fd<0)
return;
close(fd);
slash = strrchr(resume_file,'/');
if (slash)
{
char* cp;
*slash=0;
cp=resume_file;
if (!cp[0])
cp="/";
if (playlist_create(cp, slash+1) != -1)
{
if (global_settings.playlist_shuffle)
playlist_shuffle(seed, -1);
playlist_start(index,offset);
}
*slash='/';
}
}
else
{
/* Directory playback */
lastdir[0]='\0';
if (playlist_create(resume_file, NULL) != -1)
{
char* peek_filename;
resume_directory(resume_file);
if (global_settings.playlist_shuffle)
playlist_shuffle(seed, -1);
/* Check if the file is at the same spot in the directory,
else search for it */
peek_filename = playlist_peek(index);
if (peek_filename == NULL)
return;
if (strcmp(strrchr(peek_filename, '/') + 1, filename))
{
for ( i=0; i < playlist_amount(); i++ )
{
peek_filename = playlist_peek(i);
if (peek_filename == NULL)
return;
if (!strcmp(strrchr(peek_filename, '/') + 1, filename))
break;
}
if (i < playlist_amount())
index = i;
else
return;
}
playlist_start(index,offset);
}
}
start_wps=true;
}
static int ft_play_filenumber(int pos, int attr)
{
/* try to find a voice ID for the extension, if known */
int j;
int ext_id = -1; /* default to none */
for (j=0; j<filetypes_count; j++)
{
if (attr == filetypes[j].tree_attr)
{
ext_id = filetypes[j].voiceclip;
break;
}
}
talk_id(VOICE_FILE, false);
talk_number(pos, true);
talk_id(ext_id, true);
return 1;
}
static int ft_play_dirname(char* name)
{
int fd;
char dirname_mp3_filename[MAX_PATH+1];
#if CONFIG_CODEC != SWCODEC
if (audio_status() & AUDIO_STATUS_PLAY)
return 0;
#endif
snprintf(dirname_mp3_filename, sizeof(dirname_mp3_filename), "%s/%s/%s",
tc.currdir[1] ? tc.currdir : "" , name,
dir_thumbnail_name);
DEBUGF("Checking for %s\n", dirname_mp3_filename);
fd = open(dirname_mp3_filename, O_RDONLY);
if (fd < 0)
{
DEBUGF("Failed to find: %s\n", dirname_mp3_filename);
return -1;
}
close(fd);
DEBUGF("Found: %s\n", dirname_mp3_filename);
talk_file(dirname_mp3_filename, false);
return 1;
}
static void ft_play_filename(char *dir, char *file)
{
char name_mp3_filename[MAX_PATH+1];
#if CONFIG_CODEC != SWCODEC
if (audio_status() & AUDIO_STATUS_PLAY)
return;
#endif
if (strcasecmp(&file[strlen(file) - strlen(file_thumbnail_ext)],
file_thumbnail_ext))
{ /* file has no .talk extension */
snprintf(name_mp3_filename, sizeof(name_mp3_filename),
"%s/%s%s", dir, file, file_thumbnail_ext);
talk_file(name_mp3_filename, false);
}
else
{ /* it already is a .talk file, play this directly */
snprintf(name_mp3_filename, sizeof(name_mp3_filename),
"%s/%s", dir, file);
talk_id(LANG_VOICE_DIR_HOVER, false); /* prefix it */
talk_file(name_mp3_filename, true);
}
}
/* These two functions are called by the USB and shutdown handlers */
void tree_flush(void)
{
scrobbler_shutdown();
#ifdef HAVE_TAGCACHE
tagcache_shutdown();
#endif
playlist_shutdown();
#ifdef HAVE_TC_RAMCACHE
tagcache_unload_ramcache();
#endif
#ifdef HAVE_DIRCACHE
{
int old_val = global_status.dircache_size;
if (global_settings.dircache)
{
global_status.dircache_size = dircache_get_cache_size();
# ifdef HAVE_EEPROM_SETTINGS
if (firmware_settings.initialized)
dircache_save();
# endif
dircache_disable();
}
else
{
global_status.dircache_size = 0;
}
if (old_val != global_status.dircache_size)
status_save();
}
#endif
}
void tree_restore(void)
{
#ifdef HAVE_EEPROM_SETTINGS
firmware_settings.disk_clean = false;
#endif
#ifdef HAVE_TC_RAMCACHE
remove(TAGCACHE_STATEFILE);
#endif
#ifdef HAVE_DIRCACHE
remove(DIRCACHE_FILE);
if (global_settings.dircache)
{
/* Print "Scanning disk..." to the display. */
int i;
FOR_NB_SCREENS(i)
{
screens[i].putsxy((LCD_WIDTH/2) -
((strlen(str(LANG_SCANNING_DISK)) *
screens[i].char_width)/2),
LCD_HEIGHT-screens[i].char_height*3,
str(LANG_SCANNING_DISK));
gui_textarea_update(&screens[i]);
}
dircache_build(global_status.dircache_size);
/* Clean the text when we are done. */
FOR_NB_SCREENS(i)
{
gui_textarea_clear(&screens[i]);
}
}
#endif
#ifdef HAVE_TAGCACHE
tagcache_start_scan();
#endif
scrobbler_init();
}