From ab78b0468088e9011273edc32d59145db9030a7e Mon Sep 17 00:00:00 2001 From: Miika Pekkarinen Date: Fri, 7 Oct 2005 17:38:05 +0000 Subject: [PATCH] Implemented directory caching. No more waiting for disk to spin up while browsing when cache is enabled (system -> disk -> enable directory cache). Cache building on boot is transparent except the first boot. git-svn-id: svn://svn.rockbox.org/rockbox/trunk@7588 a1c6a512-1295-4272-9138-f99709370657 --- apps/filetree.c | 21 +- apps/lang/english.lang | 18 + apps/lang/finnish.lang | 18 + apps/main.c | 37 +- apps/settings.c | 6 + apps/settings.h | 5 + apps/settings_menu.c | 22 + apps/tree.c | 27 +- firmware/SOURCES | 1 + firmware/common/dir.c | 14 +- firmware/common/dircache.c | 792 ++++++++++++++++++++++++++++++++++++ firmware/common/file.c | 23 +- firmware/export/config.h | 7 + firmware/include/dircache.h | 96 +++++ firmware/include/file.h | 2 + 15 files changed, 1068 insertions(+), 21 deletions(-) create mode 100644 firmware/common/dircache.c create mode 100644 firmware/include/dircache.h diff --git a/apps/filetree.c b/apps/filetree.c index 8ecdc0c93d..37a66c60ee 100644 --- a/apps/filetree.c +++ b/apps/filetree.c @@ -38,6 +38,7 @@ #include "plugin.h" #include "rolo.h" #include "sprintf.h" +#include "dircache.h" #ifndef SIMULATOR static int boot_size = 0; @@ -75,11 +76,11 @@ int ft_build_playlist(struct tree_context* c, int start_index) static void check_file_thumbnails(struct tree_context* c) { int i; - struct dirent *entry; + struct dircache_entry *entry; struct entry* dircache = c->dircache; - DIR *dir; + DIRCACHED *dir; - dir = opendir(c->currdir); + dir = opendir_cached(c->currdir); if(!dir) return; @@ -100,7 +101,7 @@ static void check_file_thumbnails(struct tree_context* c) } } - while((entry = readdir(dir)) != 0) /* walk directory */ + while((entry = readdir_cached(dir)) != 0) /* walk directory */ { int ext_pos; @@ -126,7 +127,7 @@ static void check_file_thumbnails(struct tree_context* c) } } } - closedir(dir); + closedir_cached(dir); } /* support function for qsort() */ @@ -191,12 +192,12 @@ int ft_load(struct tree_context* c, const char* tempdir) { int i; int name_buffer_used = 0; - DIR *dir; + DIRCACHED *dir; if (tempdir) - dir = opendir(tempdir); + dir = opendir_cached(tempdir); else - dir = opendir(c->currdir); + dir = opendir_cached(c->currdir); if(!dir) return -1; /* not a directory */ @@ -205,7 +206,7 @@ int ft_load(struct tree_context* c, const char* tempdir) for ( i=0; i < global_settings.max_files_in_dir; i++ ) { int len; - struct dirent *entry = readdir(dir); + struct dircache_entry *entry = readdir_cached(dir); struct entry* dptr = (struct entry*)(c->dircache + i * sizeof(struct entry)); if (!entry) @@ -292,7 +293,7 @@ int ft_load(struct tree_context* c, const char* tempdir) } c->filesindir = i; c->dirlength = i; - closedir(dir); + closedir_cached(dir); qsort(c->dircache,i,sizeof(struct entry),compare); diff --git a/apps/lang/english.lang b/apps/lang/english.lang index 97f714609d..fbba81250f 100644 --- a/apps/lang/english.lang +++ b/apps/lang/english.lang @@ -3298,3 +3298,21 @@ desc: use track gain if shuffle mode is on, album gain otherwise eng: "Track gain if shuffling" voice: "Track gain if shuffling" new: + +id: LANG_DIRCACHE_ENABLE +desc: in directory cache settings +eng: "Enable directory cache" +voice: "Enable directory cache" +new: + +id: LANG_DIRCACHE_REBOOT +desc: when activating directory cache +eng: "Please reboot to enable the cache" +voice: "Please reboot to enable the cache" +new: + +id: LANG_DIRCACHE_BUILDING +desc: when booting up and rebuilding the cache +eng: "Scanning disk..." +voice: "" +new: diff --git a/apps/lang/finnish.lang b/apps/lang/finnish.lang index 24c46b3bce..1e6a7eb52e 100644 --- a/apps/lang/finnish.lang +++ b/apps/lang/finnish.lang @@ -3329,3 +3329,21 @@ desc: repeat one song eng: "A-B" voice: "A-B" new: "A-B" + +id: LANG_DIRCACHE_ENABLE +desc: in directory cache settings +eng: "Enable directory cache" +voice: "Hakemistopuun lataus muistiin" +new: "Hakemistopuun lataus muistiin" + +id: LANG_DIRCACHE_REBOOT +desc: when activating directory cache +eng: "Please reboot to enable the cache" +voice: "Käynnistä laite uudelleen" +new: "Käynnistä laite uudelleen" + +id: LANG_DIRCACHE_BUILDING +desc: when booting up and rebuilding the cache +eng: "Scanning disk..." +voice: "" +new: "Ladataan hakemistopuu..." diff --git a/apps/main.c b/apps/main.c index c272dba57e..73bf614cb3 100644 --- a/apps/main.c +++ b/apps/main.c @@ -59,6 +59,9 @@ #include "plugin.h" #include "misc.h" #include "database.h" +#include "dircache.h" +#include "lang.h" +#include "string.h" #if (CONFIG_CODEC == SWCODEC) #include "pcmbuf.h" @@ -92,6 +95,33 @@ void app_main(void) browse_root(); } +#ifdef HAVE_DIRCACHE +void init_dircache(void) +{ + int font_w, font_h; + + dircache_init(); + if (global_settings.dircache) + { + /* Print "Scanning disk..." to the display. */ + lcd_getstringsize("A", &font_w, &font_h); + lcd_putsxy((LCD_WIDTH/2) - ((strlen(str(LANG_DIRCACHE_BUILDING))*font_w)/2), + LCD_HEIGHT-font_h*3, str(LANG_DIRCACHE_BUILDING)); + lcd_update(); + + dircache_build(global_settings.dircache_size); + + /* Clean the text when we are done. */ + lcd_set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID); + lcd_fillrect(0, LCD_HEIGHT-font_h*3, LCD_WIDTH, font_h); + lcd_set_drawmode(DRMODE_SOLID); + lcd_update(); + } +} +#else +# define init_dircache(...) +#endif + #ifdef SIMULATOR void init(void) @@ -110,6 +140,7 @@ void init(void) settings_calc_config_sector(); settings_load(SETTINGS_ALL); settings_apply(); + init_dircache(); sleep(HZ/2); tree_init(); playlist_init(); @@ -261,13 +292,15 @@ void init(void) } } + settings_calc_config_sector(); + settings_load(SETTINGS_ALL); + init_dircache(); + /* On software codec platforms we have to init audio before calling audio_set_buffer_margin(). */ #if (CONFIG_CODEC == SWCODEC) audio_init(); #endif - settings_calc_config_sector(); - settings_load(SETTINGS_ALL); settings_apply(); status_init(); diff --git a/apps/settings.c b/apps/settings.c index 5495ca7098..6935f35eb5 100644 --- a/apps/settings.c +++ b/apps/settings.c @@ -67,6 +67,8 @@ #include "version.h" #include "rtc.h" #include "sound.h" +#include "dircache.h" + #if CONFIG_CODEC == MAS3507D void dac_line_in(bool enable); #endif @@ -439,6 +441,10 @@ static const struct bit_entry hd_bits[] = {8 | SIGNED, S_O(replaygain_preamp), 0, "replaygain preamp", NULL }, {2, S_O(beep), 0, "off,weak,moderate,strong", NULL }, #endif +#ifdef HAVE_DIRCACHE + {1, S_O(dircache), false, "dircache", off_on }, + {22, S_O(dircache_size), 0, NULL, NULL }, +#endif /* If values are just added to the end, no need to bump the version. */ /* new stuff to be added at the end */ diff --git a/apps/settings.h b/apps/settings.h index dafa7bcd34..99254e377f 100644 --- a/apps/settings.h +++ b/apps/settings.h @@ -23,6 +23,7 @@ #include #include "config.h" #include "file.h" +#include "dircache.h" #include "timefuncs.h" #include "abrepeat.h" @@ -339,6 +340,10 @@ struct user_settings int replaygain_preamp; /* scale replaygained tracks by this */ int beep; /* system beep volume when changing tracks etc. */ #endif +#ifdef HAVE_DIRCACHE + bool dircache; /* enable directory cache */ + int dircache_size; /* directory cache structure last size, 22 bits */ +#endif }; enum optiontype { INT, BOOL }; diff --git a/apps/settings_menu.c b/apps/settings_menu.c index 8a1fa72415..6ad5274f0f 100644 --- a/apps/settings_menu.c +++ b/apps/settings_menu.c @@ -49,6 +49,8 @@ #include "abrepeat.h" #include "power.h" #include "database.h" +#include "dir.h" +#include "dircache.h" #ifdef HAVE_LCD_BITMAP #include "peakmeter.h" @@ -1259,6 +1261,23 @@ static bool beep(void) } #endif +#ifdef HAVE_DIRCACHE +static bool dircache(void) +{ + bool result = set_bool(str(LANG_DIRCACHE_ENABLE), + &global_settings.dircache); + + if (!dircache_is_enabled() && global_settings.dircache) + splash(HZ*2, true, str(LANG_DIRCACHE_REBOOT)); + + if (!result) + dircache_disable(); + + return result; +} + +#endif /* HAVE_DIRCACHE */ + static bool playback_settings_menu(void) { int m; @@ -1573,6 +1592,9 @@ static bool disk_settings_menu(void) { ID2P(LANG_SPINDOWN), spindown }, #ifdef HAVE_ATA_POWER_OFF { ID2P(LANG_POWEROFF), poweroff }, +#endif +#ifdef HAVE_DIRCACHE + { ID2P(LANG_DIRCACHE_ENABLE), dircache }, #endif }; diff --git a/apps/tree.c b/apps/tree.c index a52d453081..bfaf7b8304 100644 --- a/apps/tree.c +++ b/apps/tree.c @@ -61,6 +61,7 @@ #include "dbtree.h" #include "recorder/recording.h" #include "rtc.h" +#include "dircache.h" #ifdef HAVE_LCD_BITMAP #include "widgets.h" @@ -1324,7 +1325,7 @@ static int plsize = 0; static bool add_dir(char* dirname, int len, int fd) { bool abort = false; - DIR* dir; + DIRCACHED* dir; /* check for user abort */ #ifdef BUTTON_STOP @@ -1334,14 +1335,14 @@ static bool add_dir(char* dirname, int len, int fd) #endif return true; - dir = opendir(dirname); + dir = opendir_cached(dirname); if(!dir) return true; while (true) { - struct dirent *entry; + struct dircache_entry *entry; - entry = readdir(dir); + entry = readdir_cached(dir); if (!entry) break; if (entry->attribute & ATTR_DIRECTORY) { @@ -1409,7 +1410,7 @@ static bool add_dir(char* dirname, int len, int fd) } } } - closedir(dir); + closedir_cached(dir); return abort; } @@ -1634,10 +1635,26 @@ void tree_flush(void) { rundb_shutdown(); tagdb_shutdown(); +#ifdef HAVE_DIRCACHE + if (global_settings.dircache) + { + global_settings.dircache_size = dircache_get_cache_size(); + dircache_disable(); + } + else + { + global_settings.dircache_size = 0; + } + settings_save(); +#endif } void tree_restore(void) { tagdb_init(); rundb_init(); +#ifdef HAVE_DIRCACHE + if (global_settings.dircache) + dircache_build(global_settings.dircache_size); +#endif } diff --git a/firmware/SOURCES b/firmware/SOURCES index a1afa4daea..febc794010 100644 --- a/firmware/SOURCES +++ b/firmware/SOURCES @@ -11,6 +11,7 @@ common/ctype.c common/dir.c common/file.c #endif +common/dircache.c common/disk.c common/errno.c common/memcmp.c diff --git a/firmware/common/dir.c b/firmware/common/dir.c index aa55aeb9e9..7361c78067 100644 --- a/firmware/common/dir.c +++ b/firmware/common/dir.c @@ -24,6 +24,7 @@ #include "dir.h" #include "debug.h" #include "atoi.h" +#include "dircache.h" #define MAX_OPEN_DIRS 8 @@ -86,7 +87,6 @@ int release_dirs(int volume) } #endif /* #ifdef HAVE_HOTSWAP */ - DIR* opendir(const char* name) { char namecopy[MAX_PATH]; @@ -275,7 +275,11 @@ int mkdir(const char *name, int mode) memset(&newdir, sizeof(struct fat_dir), 0); rc = fat_create_dir(basename, &newdir, &(dir->fatdir)); - +#ifdef HAVE_DIRCACHE + if (rc >= 0) + dircache_mkdir(name); +#endif + closedir(dir); return rc; @@ -313,6 +317,12 @@ int rmdir(const char* name) errno = EIO; rc = rc * 10 - 3; } +#ifdef HAVE_DIRCACHE + else + { + dircache_rmdir(name); + } +#endif closedir(dir); diff --git a/firmware/common/dircache.c b/firmware/common/dircache.c new file mode 100644 index 0000000000..a777ea9574 --- /dev/null +++ b/firmware/common/dircache.c @@ -0,0 +1,792 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2005 by Miika Pekkarinen + * + * 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. + * + ****************************************************************************/ + +/* TODO: + - Allow cache live updating while transparent rebuild is running. + - Fix this to work with simulator (opendir & readdir) again. +*/ + +#include "config.h" + +#ifdef HAVE_DIRCACHE + +#include +#include +#include +#include +#include "dir.h" +#include "debug.h" +#include "atoi.h" +#include "system.h" +#include "logf.h" +#include "dircache.h" +#include "thread.h" +#include "kernel.h" +#include "usb.h" +#include "file.h" + +/* Queue commands. */ +#define DIRCACHE_BUILD 1 + +extern char *audiobuf; + +#define MAX_OPEN_DIRS 8 +DIRCACHED opendirs[MAX_OPEN_DIRS]; + +static struct dircache_entry *fd_bindings[MAX_OPEN_FILES]; +static struct dircache_entry *dircache_root; + +static bool dircache_initialized = false; +static bool thread_enabled = false; +static unsigned long allocated_size = DIRCACHE_LIMIT; +static unsigned long dircache_size = 0; +static unsigned long reserve_used = 0; +static char dircache_cur_path[MAX_PATH]; + +static struct event_queue dircache_queue; +static long dircache_stack[(DEFAULT_STACK_SIZE + 0x800)/sizeof(long)]; +static const char dircache_thread_name[] = "dircache"; + +/* --- Internal cache structure control functions --- */ +static struct dircache_entry* allocate_entry(void) +{ + struct dircache_entry *next_entry; + + if (dircache_size > allocated_size - MAX_PATH*2) + { + logf("size limit reached"); + return NULL; + } + + next_entry = (struct dircache_entry *)((char *)dircache_root+dircache_size); + dircache_size += sizeof(struct dircache_entry); + next_entry->name_len = 0; + next_entry->d_name = NULL; + next_entry->down = NULL; + next_entry->next = NULL; + + return next_entry; +} + +static struct dircache_entry* dircache_gen_next(struct dircache_entry *ce) +{ + struct dircache_entry *next_entry; + + next_entry = allocate_entry(); + ce->next = next_entry; + + return next_entry; +} + +static struct dircache_entry* dircache_gen_down(struct dircache_entry *ce) +{ + struct dircache_entry *next_entry; + + next_entry = allocate_entry(); + ce->down = next_entry; + + return next_entry; +} + +/* This will eat ~30 KiB of memory! + * We should probably use that as additional reserve buffer in future. */ +#define MAX_SCAN_DEPTH 16 +static struct travel_data dir_recursion[MAX_SCAN_DEPTH]; + +static int dircache_scan(struct travel_data *td) +{ + while ( (fat_getnext(td->dir, &td->entry) >= 0) && (td->entry.name[0])) + { + if (thread_enabled) + { + /* Stop if we got an external signal. */ + if (!queue_empty(&dircache_queue)) + return -6; + yield(); + } + + if (!strcmp(".", td->entry.name) || + !strcmp("..", td->entry.name)) + { + continue; + } + + td->ce->attribute = td->entry.attr; + td->ce->name_len = MIN(254, strlen(td->entry.name)) + 1; + td->ce->d_name = ((char *)dircache_root+dircache_size); + td->ce->startcluster = td->entry.firstcluster; + td->ce->size = td->entry.filesize; + td->ce->wrtdate = td->entry.wrtdate; + td->ce->wrttime = td->entry.wrttime; + memcpy(td->ce->d_name, td->entry.name, td->ce->name_len); + dircache_size += td->ce->name_len; + + if (td->entry.attr & FAT_ATTR_DIRECTORY) + { + + td->down_entry = dircache_gen_down(td->ce); + if (td->down_entry == NULL) + return -2; + + td->pathpos = strlen(dircache_cur_path); + strncpy(&dircache_cur_path[td->pathpos], "/", MAX_PATH - td->pathpos - 1); + strncpy(&dircache_cur_path[td->pathpos+1], td->entry.name, MAX_PATH - td->pathpos - 2); + + td->newdir = *td->dir; + if (fat_opendir(IF_MV2(volume,) &td->newdir, + td->entry.firstcluster, td->dir) < 0 ) + { + return -3; + } + + td->ce = dircache_gen_next(td->ce); + if (td->ce == NULL) + return -4; + + return 1; + } + + td->ce->down = NULL; + td->ce = dircache_gen_next(td->ce); + if (td->ce == NULL) + return -5; + } + + return 0; +} + +/* Recursively scan the hard disk and build the cache. */ +static int dircache_travel(struct fat_dir *dir, struct dircache_entry *ce) +{ + int depth = 0; + int result; + + dir_recursion[0].dir = dir; + dir_recursion[0].ce = ce; + + do { + //logf("=> %s", dircache_cur_path); + result = dircache_scan(&dir_recursion[depth]); + switch (result) { + case 0: /* Leaving the current directory. */ + depth--; + if (depth >= 0) + dircache_cur_path[dir_recursion[depth].pathpos] = '\0'; + break ; + + case 1: /* Going down in the directory tree. */ + depth++; + if (depth >= MAX_SCAN_DEPTH) + { + logf("Too deep directory structure"); + return -2; + } + + dir_recursion[depth].dir = &dir_recursion[depth-1].newdir; + dir_recursion[depth].ce = dir_recursion[depth-1].down_entry; + break ; + + default: + logf("Scan failed"); + logf("-> %s", dircache_cur_path); + return -1; + } + } while (depth >= 0) ; + + return 0; +} + +static struct dircache_entry* dircache_get_entry(const char *path, + bool get_before, bool only_directories) +{ + struct dircache_entry *cache_entry, *before; + char namecopy[MAX_PATH]; + char* part; + char* end; + + strncpy(namecopy, path, sizeof(namecopy) - 1); + cache_entry = dircache_root; + before = NULL; + + for ( part = strtok_r(namecopy, "/", &end); part; + part = strtok_r(NULL, "/", &end)) { + + /* scan dir for name */ + while (1) + { + if (cache_entry == NULL) + { + return NULL; + } + else if (cache_entry->name_len == 0) + { + cache_entry = cache_entry->next; + continue ; + } + + if (!strcasecmp(part, cache_entry->d_name)) + { + before = cache_entry; + if (cache_entry->down || only_directories) + cache_entry = cache_entry->down; + break ; + } + + cache_entry = cache_entry->next; + } + } + + if (get_before) + cache_entry = before; + + return cache_entry; +} + +#if 0 +int dircache_load(const char *path) +{ + struct dircache_maindata maindata; + int bytes_read; + int fd; + + if (dircache_initialized) + return -1; + + logf("Loading directory cache"); + dircache_size = 0; + + fd = open(path, O_RDONLY); + if (fd < 0) + return -2; + + bytes_read = read(fd, &maindata, sizeof(struct dircache_maindata)); + if (bytes_read != sizeof(struct dircache_maindata) + || (long)maindata.root_entry != (long)audiobuf + || maindata.size <= 0) + { + close(fd); + return -3; + } + + dircache_root = (struct dircache_entry *)audiobuf; + bytes_read = read(fd, dircache_root, MIN(DIRCACHE_LIMIT, maindata.size)); + close(fd); + + if (bytes_read != maindata.size) + return -6; + + /* Cache successfully loaded. */ + dircache_size = maindata.size; + logf("Done, %d KiB used", dircache_size / 1024); + dircache_initialized = true; + memset(fd_bindings, 0, sizeof(fd_bindings)); + + /* We have to long align the audiobuf to keep the buffer access fast. */ + audiobuf += (long)((dircache_size & ~0x03) + 0x04); + audiobuf += DIRCACHE_RESERVE; + + return 0; +} + +int dircache_save(const char *path) +{ + struct dircache_maindata maindata; + int fd; + unsigned long bytes_written; + + remove(path); + + while (thread_enabled) + sleep(1); + + if (!dircache_initialized) + return -1; + + logf("Saving directory cache"); + fd = open(path, O_WRONLY | O_CREAT); + + maindata.magic = DIRCACHE_MAGIC; + maindata.size = dircache_size; + maindata.root_entry = dircache_root; + + /* Save the info structure */ + bytes_written = write(fd, &maindata, sizeof(struct dircache_maindata)); + if (bytes_written != sizeof(struct dircache_maindata)) + { + close(fd); + return -2; + } + + /* Dump whole directory cache to disk */ + bytes_written = write(fd, dircache_root, dircache_size); + close(fd); + if (bytes_written != dircache_size) + return -3; + + return 0; +} +#endif /* #if 0 */ + +static int dircache_do_rebuild(void) +{ + struct fat_dir dir; + + if ( fat_opendir(IF_MV2(volume,) &dir, 0, NULL) < 0 ) { + logf("Failed opening root dir"); + return -3; + } + + //return -5; +/* dir = opendir("/"); + if (dir == NULL) + { + logf("failed to open rootdir"); + return -1; +}*/ + + memset(dircache_cur_path, 0, MAX_PATH); + dircache_size = sizeof(struct dircache_entry); + + cpu_boost(true); + if (dircache_travel(&dir, dircache_root) < 0) + { + logf("dircache_travel failed"); + cpu_boost(false); + dircache_size = 0; + return -2; + } + cpu_boost(false); + + logf("Done, %d KiB used", dircache_size / 1024); + dircache_initialized = true; + + if (thread_enabled) + { + if (allocated_size - dircache_size < DIRCACHE_RESERVE) + reserve_used = DIRCACHE_RESERVE - (allocated_size - dircache_size); + } + else + { + /* We have to long align the audiobuf to keep the buffer access fast. */ + audiobuf += (long)((dircache_size & ~0x03) + 0x04); + audiobuf += DIRCACHE_RESERVE; + allocated_size = dircache_size + DIRCACHE_RESERVE; + } + + return 1; +} + +static void dircache_thread(void) +{ + struct event ev; + + while (1) + { + queue_wait(&dircache_queue, &ev); + + switch (ev.id) + { + case DIRCACHE_BUILD: + thread_enabled = true; + dircache_do_rebuild(); + thread_enabled = false; + break ; + +#ifndef SIMULATOR + case SYS_USB_CONNECTED: + usb_acknowledge(SYS_USB_CONNECTED_ACK); + usb_wait_for_disconnect(&dircache_queue); + break ; +#endif + } + } +} + +int dircache_build(int last_size) +{ + if (dircache_initialized) + return -3; + + while (thread_enabled) + sleep(1); + + logf("Building directory cache"); + if (dircache_size > 0) + { + allocated_size = dircache_size + (DIRCACHE_RESERVE-reserve_used); + thread_enabled = true; + queue_post(&dircache_queue, DIRCACHE_BUILD, 0); + return 2; + } + else + { + dircache_root = (struct dircache_entry *)audiobuf; + dircache_size = 0; + } + + if (last_size > DIRCACHE_RESERVE && last_size < DIRCACHE_LIMIT) + { + allocated_size = last_size + DIRCACHE_RESERVE; + + /* We have to long align the audiobuf to keep the buffer access fast. */ + audiobuf += (long)((allocated_size & ~0x03) + 0x04); + thread_enabled = true; + + /* Start a transparent rebuild. */ + queue_post(&dircache_queue, DIRCACHE_BUILD, 0); + return 3; + } + + /* Start a non-transparent rebuild. */ + return dircache_do_rebuild(); +} + +void dircache_init(void) +{ + int i; + + memset(opendirs, 0, sizeof(opendirs)); + for (i = 0; i < MAX_OPEN_DIRS; i++) + { + opendirs[i].secondary_entry.d_name = audiobuf; + audiobuf += MAX_PATH; + } + + queue_init(&dircache_queue); + create_thread(dircache_thread, dircache_stack, + sizeof(dircache_stack), dircache_thread_name); +} + +bool dircache_is_enabled(void) +{ + return dircache_initialized; +} + +int dircache_get_cache_size(void) +{ + if (!dircache_is_enabled()) + return 0; + + return dircache_size; +} + +void dircache_disable(void) +{ + int i; + + while (thread_enabled) + sleep(1); + dircache_initialized = false; + + for (i = 0; i < MAX_OPEN_DIRS; i++) + opendirs[i].busy = false; +} + +/* --- Directory cache live updating functions --- */ +static struct dircache_entry* dircache_new_entry(const char *path, int attribute) +{ + struct dircache_entry *entry; + char basedir[MAX_PATH]; + char *new; + long last_cache_size = dircache_size; + + strncpy(basedir, path, sizeof(basedir)-1); + new = strrchr(basedir, '/'); + if (new == NULL) + { + logf("error occurred"); + dircache_initialized = false; + return NULL; + } + + *new = '\0'; + new++; + + entry = dircache_get_entry(basedir, false, true); + if (entry == NULL) + { + logf("basedir not found!"); + dircache_initialized = false; + return NULL; + } + + if (reserve_used + 2*sizeof(struct dircache_entry) + strlen(new)+1 + >= DIRCACHE_RESERVE) + { + logf("not enough space"); + dircache_initialized = false; + return NULL; + } + + while (entry->next != NULL) + entry = entry->next; + + if (entry->name_len > 0) + entry = dircache_gen_next(entry); + + if (entry == NULL) + { + dircache_initialized = false; + return NULL; + } + + entry->attribute = attribute; + entry->name_len = MIN(254, strlen(new)) + 1; + entry->d_name = ((char *)dircache_root+dircache_size); + entry->startcluster = 0; + entry->wrtdate = 0; + entry->wrttime = 0; + entry->size = 0; + memcpy(entry->d_name, new, entry->name_len); + dircache_size += entry->name_len; + + if (attribute & ATTR_DIRECTORY) + { + logf("gen_down"); + dircache_gen_down(entry); + } + + reserve_used += dircache_size - last_cache_size; + + return entry; +} + +void dircache_bind(int fd, const char *path) +{ + struct dircache_entry *entry; + + if (!dircache_initialized) + return ; + + logf("bind: %d/%s", fd, path); + entry = dircache_get_entry(path, false, false); + if (entry == NULL) + { + logf("not found!"); + dircache_initialized = false; + return ; + } + + fd_bindings[fd] = entry; +} + +void dircache_update_filesize(int fd, long newsize) +{ + if (!dircache_initialized || fd < 0) + return ; + + fd_bindings[fd]->size = newsize; +} + +void dircache_mkdir(const char *path) +{ /* Test ok. */ + if (!dircache_initialized) + return ; + + logf("mkdir: %s", path); + dircache_new_entry(path, ATTR_DIRECTORY); +} + +void dircache_rmdir(const char *path) +{ /* Test ok. */ + struct dircache_entry *entry; + + if (!dircache_initialized) + return ; + + logf("rmdir: %s", path); + entry = dircache_get_entry(path, true, true); + if (entry == NULL) + { + logf("not found!"); + dircache_initialized = false; + return ; + } + + entry->down = NULL; + entry->name_len = 0; +} + +/* Remove a file from cache */ +void dircache_remove(const char *name) +{ /* Test ok. */ + struct dircache_entry *entry; + + if (!dircache_initialized) + return ; + + logf("remove: %s", name); + + entry = dircache_get_entry(name, false, false); + + if (entry == NULL) + { + logf("not found!"); + dircache_initialized = false; + return ; + } + + entry->name_len = 0; +} + +void dircache_rename(const char *oldpath, const char *newpath) +{ /* Test ok. */ + struct dircache_entry *entry, *newentry; + + if (!dircache_initialized) + return ; + + logf("rename: %s->%s", oldpath, newpath); + entry = dircache_get_entry(oldpath, true, false); + if (entry == NULL) + { + logf("not found!"); + dircache_initialized = false; + return ; + } + + /* Delete the old entry. */ + entry->name_len = 0; + + newentry = dircache_new_entry(newpath, entry->attribute); + if (newentry == NULL) + { + dircache_initialized = false; + return ; + } + + //newentry->down = entry->down; + //entry->down = 0; + + newentry->size = entry->size; + newentry->startcluster = entry->startcluster; + newentry->wrttime = entry->wrttime; + newentry->wrtdate = entry->wrtdate; +} + +void dircache_add_file(const char *path) +{ + if (!dircache_initialized) + return ; + + logf("add file: %s", path); + dircache_new_entry(path, 0); +} + +DIRCACHED* opendir_cached(const char* name) +{ + struct dircache_entry *cache_entry; + int dd; + DIRCACHED* pdir = opendirs; + + if ( name[0] != '/' ) + { + DEBUGF("Only absolute paths supported right now\n"); + return NULL; + } + + /* find a free dir descriptor */ + for ( dd=0; ddbusy ) + break; + + if ( dd == MAX_OPEN_DIRS ) + { + DEBUGF("Too many dirs open\n"); + errno = EMFILE; + return NULL; + } + + if (!dircache_initialized) + { + pdir->regulardir = opendir(name); + if (!pdir->regulardir) + return NULL; + + pdir->busy = true; + return pdir; + } + + pdir->busy = true; + pdir->regulardir = NULL; + cache_entry = dircache_get_entry(name, false, true); + pdir->entry = cache_entry; + + if (cache_entry == NULL) + { + pdir->busy = false; + return NULL; + } + + return pdir; +} + +struct dircache_entry* readdir_cached(DIRCACHED* dir) +{ + struct dirent *regentry; + struct dircache_entry *ce; + + if (!dir->busy) + return NULL; + + if (!dircache_initialized) + { + if (dir->regulardir == NULL) + return NULL; + regentry = readdir(dir->regulardir); + if (regentry == NULL) + return NULL; + + strncpy(dir->secondary_entry.d_name, regentry->d_name, MAX_PATH-1); + dir->secondary_entry.size = regentry->size; + dir->secondary_entry.startcluster = regentry->startcluster; + dir->secondary_entry.attribute = regentry->attribute; + dir->secondary_entry.wrttime = regentry->wrttime; + dir->secondary_entry.wrtdate = regentry->wrtdate; + dir->secondary_entry.next = NULL; + + return &dir->secondary_entry; + } + + do { + if (dir->entry == NULL) + return NULL; + + ce = dir->entry; + if (ce->name_len == 0) + dir->entry = ce->next; + } while (ce->name_len == 0) ; + + dir->entry = ce->next; + + //logf("-> %s", ce->name); + return ce; +} + +int closedir_cached(DIRCACHED* dir) +{ + dir->busy=false; + if (!dircache_initialized && dir->regulardir != NULL) + return closedir(dir->regulardir); + + return 0; +} + +#endif /* HAVE_DIRCACHE */ + diff --git a/firmware/common/file.c b/firmware/common/file.c index 795fc7df7e..01ae17224d 100644 --- a/firmware/common/file.c +++ b/firmware/common/file.c @@ -23,6 +23,7 @@ #include "fat.h" #include "dir.h" #include "debug.h" +#include "dircache.h" /* These functions provide a roughly POSIX-compatible file IO API. @@ -33,8 +34,6 @@ The penalty is the RAM used for the cache and slightly more complex code. */ -#define MAX_OPEN_FILES 8 - struct filedesc { unsigned char cache[SECTOR_SIZE]; int cacheoffset; /* invariant: 0 <= cacheoffset <= SECTOR_SIZE */ @@ -155,6 +154,9 @@ int open(const char* pathname, int flags) closedir(dir); return rc * 10 - 6; } +#ifdef HAVE_DIRCACHE + dircache_add_file(pathname); +#endif file->size = 0; file->attr = 0; } @@ -184,6 +186,11 @@ int open(const char* pathname, int flags) return rc * 10 - 9; } +#ifdef HAVE_DIRCACHE + if (file->write) + dircache_bind(fd, pathname); +#endif + return fd; } @@ -267,6 +274,9 @@ int remove(const char* name) } file->size = 0; +#ifdef HAVE_DIRCACHE + dircache_remove(name); +#endif rc = close(fd); if (rc<0) @@ -335,6 +345,10 @@ int rename(const char* path, const char* newpath) return rc * 10 - 6; } +#ifdef HAVE_DIRCACHE + dircache_rename(path, newpath); +#endif + rc = close(fd); if (rc<0) { errno = EIO; @@ -549,7 +563,12 @@ static int readwrite(int fd, void* buf, long count, bool write) /* adjust file size to length written */ if ( write && file->fileoffset > file->size ) + { file->size = file->fileoffset; +#ifdef HAVE_DIRCACHE + dircache_update_filesize(fd, file->size); +#endif + } return nread; } diff --git a/firmware/export/config.h b/firmware/export/config.h index bd817dcb2f..d47ab3d935 100644 --- a/firmware/export/config.h +++ b/firmware/export/config.h @@ -117,6 +117,13 @@ /* no known platform */ #endif +/* Enable the directory cache if we have plenty of RAM. */ +/* Cache is just temporarily disabled for simulator build. + * Do the small fix in dircache.c to enable this. */ +#if MEM > 8 && !defined(SIMULATOR) +#define HAVE_DIRCACHE 1 +#endif + /* define for all cpus from coldfire family */ #if (CONFIG_CPU == MCF5249) || (CONFIG_CPU == MCF5250) #define CPU_COLDFIRE diff --git a/firmware/include/dircache.h b/firmware/include/dircache.h new file mode 100644 index 0000000000..48b74e0220 --- /dev/null +++ b/firmware/include/dircache.h @@ -0,0 +1,96 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2005 by Miika Pekkarinen + * + * 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. + * + ****************************************************************************/ +#ifndef _DIRCACHE_H +#define _DIRCACHE_H + +#include "dir.h" + +#ifdef HAVE_DIRCACHE + +#define DIRCACHE_RESERVE (1024*64) +#define DIRCACHE_LIMIT (1024*1024*2) +#define DIRCACHE_FILE ROCKBOX_DIR "/dircache.dat" + +/* Internal structures. */ +struct travel_data { + struct dircache_entry *ce; + struct dircache_entry *down_entry; + struct fat_dir *dir; + struct fat_dir newdir; + struct fat_direntry entry; + int pathpos; +}; + +#define DIRCACHE_MAGIC 0x00d0c0a0 +struct dircache_maindata { + long magic; + long size; + struct dircache_entry *root_entry; +}; + +/* Exported structures. */ +struct dircache_entry { + struct dircache_entry *next; + struct dircache_entry *down; + int attribute; + long size; + long startcluster; + unsigned short wrtdate; + unsigned short wrttime; + unsigned char name_len; + char *d_name; +}; + +typedef struct { + bool busy; + struct dircache_entry *entry; + struct dircache_entry secondary_entry; + DIR *regulardir; +} DIRCACHED; + +void dircache_init(void); +int dircache_load(const char *path); +int dircache_save(const char *path); +int dircache_build(int last_size); +bool dircache_is_enabled(void); +int dircache_get_cache_size(void); +void dircache_disable(void); + +void dircache_bind(int fd, const char *path); +void dircache_update_filesize(int fd, long newsize); +void dircache_mkdir(const char *path); +void dircache_rmdir(const char *path); +void dircache_remove(const char *name); +void dircache_rename(const char *oldpath, const char *newpath); +void dircache_add_file(const char *path); + +DIRCACHED* opendir_cached(const char* name); +struct dircache_entry* readdir_cached(DIRCACHED* dir); +int closedir_cached(DIRCACHED *dir); + +#else /* HAVE_DIRCACHE */ +# define DIRCACHED DIR +# define dircache_entry dirent +# define opendir_cached opendir +# define closedir_cached closedir +# define readdir_cached readdir +# define closedir_cached closedir +#endif /* !HAVE_DIRCACHE */ + +#endif diff --git a/firmware/include/file.h b/firmware/include/file.h index d497ede23c..3db6507290 100644 --- a/firmware/include/file.h +++ b/firmware/include/file.h @@ -25,6 +25,8 @@ #undef MAX_PATH /* this avoids problems when building simulator */ #define MAX_PATH 260 +#define MAX_OPEN_FILES 8 + #ifndef SEEK_SET #define SEEK_SET 0 #endif