From 7c4e0c8730d5b076d4db4206361bc38d5256a23f Mon Sep 17 00:00:00 2001 From: Miika Pekkarinen Date: Sun, 26 Mar 2006 11:33:42 +0000 Subject: Initial version of tagcache! There are still some bugs in the engine and much more problems with the UI. git-svn-id: svn://svn.rockbox.org/rockbox/trunk@9256 a1c6a512-1295-4272-9138-f99709370657 --- apps/SOURCES | 6 +- apps/codecs.c | 6 - apps/codecs.h | 10 +- apps/debug_menu.c | 39 + apps/lang/english.lang | 42 + apps/main.c | 27 +- apps/metadata.c | 11 +- apps/metadata.h | 1 + apps/onplay.c | 4 +- apps/plugin.c | 10 - apps/plugin.h | 14 +- apps/plugins/Makefile | 3 +- apps/plugins/SOURCES | 2 +- apps/settings.c | 5 + apps/settings.h | 1 + apps/settings_menu.c | 22 +- apps/tagcache.c | 1868 ++++++++++++++++++++++++++++++++++++++++++++ apps/tagcache.h | 78 ++ apps/tagtree.c | 536 +++++++++++++ apps/tagtree.h | 41 + apps/tree.c | 31 +- apps/tree.h | 1 + firmware/common/dircache.c | 4 +- 23 files changed, 2698 insertions(+), 64 deletions(-) create mode 100644 apps/tagcache.c create mode 100644 apps/tagcache.h create mode 100644 apps/tagtree.c create mode 100644 apps/tagtree.h diff --git a/apps/SOURCES b/apps/SOURCES index e872531408..4c479b7a60 100644 --- a/apps/SOURCES +++ b/apps/SOURCES @@ -25,8 +25,7 @@ status.c talk.c #endif tree.c -dbtree.c -database.c +tagtree.c filetree.c screen_access.c @@ -69,7 +68,6 @@ recorder/recording.c #if CONFIG_CODEC == SWCODEC pcmbuf.c playback.c -metadata.c codecs.c dsp.c eq.c @@ -81,3 +79,5 @@ eq_arm.S #endif eq_menu.c #endif +metadata.c +tagcache.c diff --git a/apps/codecs.c b/apps/codecs.c index 298e5e1964..95a07539b9 100644 --- a/apps/codecs.c +++ b/apps/codecs.c @@ -177,12 +177,6 @@ struct codec_api ci = { audio_flush_and_reload_tracks, audio_get_file_pos, - /* tag database */ - &tagdbheader, - &tagdb_fd, - &tagdb_initialized, - tagdb_init, - /* misc */ srand, rand, diff --git a/apps/codecs.h b/apps/codecs.h index d28afc7292..50ab4cda0e 100644 --- a/apps/codecs.h +++ b/apps/codecs.h @@ -85,12 +85,12 @@ #define CODEC_MAGIC 0x52434F44 /* RCOD */ /* increase this every time the api struct changes */ -#define CODEC_API_VERSION 5 +#define CODEC_API_VERSION 6 /* update this to latest version if a change to the api struct breaks backwards compatibility (and please take the opportunity to sort in any new function which are "waiting" at the end of the function table) */ -#define CODEC_MIN_API_VERSION 5 +#define CODEC_MIN_API_VERSION 6 /* codec return codes */ enum codec_status { @@ -248,12 +248,6 @@ struct codec_api { void (*audio_flush_and_reload_tracks)(void); int (*audio_get_file_pos)(void); - /* tag database */ - struct tagdb_header *tagdbheader; - int *tagdb_fd; - int *tagdb_initialized; - int (*tagdb_init) (void); - /* misc */ void (*srand)(unsigned int seed); int (*rand)(void); diff --git a/apps/debug_menu.c b/apps/debug_menu.c index 9ab505061e..c62f65e33d 100644 --- a/apps/debug_menu.c +++ b/apps/debug_menu.c @@ -49,6 +49,8 @@ #include "screens.h" #include "misc.h" #include "splash.h" +#include "dircache.h" +#include "tagcache.h" #include "lcd-remote.h" #ifdef HAVE_LCD_BITMAP @@ -1855,6 +1857,10 @@ static bool dbg_dircache_info(void) dircache_get_build_ticks() / HZ); lcd_puts(0, line++, buf); + snprintf(buf, sizeof(buf), "Entry count: %d", + dircache_get_entry_count()); + lcd_puts(0, line++, buf); + lcd_update(); switch (button_get_w_tmo(HZ/2)) @@ -1871,6 +1877,38 @@ static bool dbg_dircache_info(void) #endif /* HAVE_DIRCACHE */ +static bool dbg_tagcache_info(void) +{ + bool done = false; + int line; + char buf[32]; + + lcd_setmargins(0, 0); + lcd_setfont(FONT_SYSFIXED); + + while (!done) + { + line = 0; + + lcd_clear_display(); + snprintf(buf, sizeof(buf), "Current progress: %d%%", + tagcache_get_progress()); + lcd_puts(0, line++, buf); + + lcd_update(); + + switch (button_get_w_tmo(HZ/2)) + { + case SETTINGS_OK: + case SETTINGS_CANCEL: + done = true; + break; + } + } + + return false; +} + #if CONFIG_CPU == SH7034 bool dbg_save_roms(void) { @@ -2014,6 +2052,7 @@ bool debug_menu(void) #ifdef HAVE_DIRCACHE { "View dircache info", dbg_dircache_info }, #endif + { "View tagcache info", dbg_tagcache_info }, #ifdef HAVE_LCD_BITMAP { "View audio thread", dbg_audio_thread }, #ifdef PM_DEBUG diff --git a/apps/lang/english.lang b/apps/lang/english.lang index 868b20646e..9923fa9caf 100644 --- a/apps/lang/english.lang +++ b/apps/lang/english.lang @@ -3838,3 +3838,45 @@ desc: Backlight behaviour setting eng: "First keypress enables backlight only" voice: "First keypress enables backlight only" new: + +id: LANG_ID3DB_GENRES +desc: in tag cache +eng: "Genres" +voice: "Genres" +new: + +id: LANG_TAGCACHE +desc: in tag cache settings +eng: "Tag cache" +voice: "Tag cache" +new: + +id: LANG_TAGCACHE_INIT +desc: while initializing tagcache on boot +eng: "Committing tagcache" +voice: +new: + +id: LANG_TAGCACHE_DISK +desc: in tag cache settings +eng: "Keep on disk" +voice: "Keep on disk" +new: + +id: LANG_TAGCACHE_RAM +desc: in tag cache settings +eng: "Load to ram" +voice: "Load to ram" +new: + +id: LANG_TAGCACHE_FORCE_UPDATE +desc: in tag cache settings +eng: "Force tag cache update" +voice: "Force tag cache update" +new: + +id: LANG_TAGCACHE_FORCE_UPDATE_SPLASH +desc: in tag cache settings +eng: "Updating in background" +voice: "Updating in background" +new: diff --git a/apps/main.c b/apps/main.c index 09c1e4bea5..915aec27d3 100644 --- a/apps/main.c +++ b/apps/main.c @@ -60,6 +60,7 @@ #include "misc.h" #include "database.h" #include "dircache.h" +#include "tagcache.h" #include "lang.h" #include "string.h" #include "splash.h" @@ -138,6 +139,29 @@ void init_dircache(void) # define init_dircache(...) #endif +void init_tagcache(void) +{ + int font_w, font_h; + +#ifdef HAVE_LCD_BITMAP + /* Print "Scanning disk..." to the display. */ + lcd_getstringsize("A", &font_w, &font_h); + lcd_putsxy((LCD_WIDTH/2) - ((strlen(str(LANG_TAGCACHE_INIT))*font_w)/2), + LCD_HEIGHT-font_h*3, str(LANG_TAGCACHE_INIT)); + lcd_update(); +#endif + + tagcache_init(); + +#ifdef HAVE_LCD_BITMAP + /* 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(); +#endif +} + #ifdef SIMULATOR void init(void) @@ -162,6 +186,7 @@ void init(void) gui_sync_wps_init(); settings_apply(); init_dircache(); + init_tagcache(); sleep(HZ/2); tree_init(); playlist_init(); @@ -350,6 +375,7 @@ void init(void) init_dircache(); + init_tagcache(); gui_sync_wps_init(); settings_apply(); @@ -379,7 +405,6 @@ void init(void) #endif talk_init(); /* runtime database has to be initialized after audio_init() */ - rundb_init(); cpu_boost(false); #ifdef AUTOROCK diff --git a/apps/metadata.c b/apps/metadata.c index 5481cbef58..09ccb3928d 100644 --- a/apps/metadata.c +++ b/apps/metadata.c @@ -68,6 +68,7 @@ static const struct format_list formats[] = { AFMT_MPA_L2, "mp2" }, { AFMT_MPA_L2, "mpa" }, { AFMT_MPA_L3, "mp3" }, +#if CONFIG_CODEC == SWCODEC { AFMT_OGG_VORBIS, "ogg" }, { AFMT_PCM_WAV, "wav" }, { AFMT_FLAC, "flac" }, @@ -80,8 +81,10 @@ static const struct format_list formats[] = { AFMT_SHN, "shn" }, { AFMT_AIFF, "aif" }, { AFMT_AIFF, "aiff" }, +#endif }; +#if CONFIG_CODEC == SWCODEC static const unsigned short a52_bitrates[] = { 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, @@ -1246,6 +1249,7 @@ static bool get_musepack_metadata(int fd, struct mp3entry *id3) id3->bitrate = id3->filesize*8/id3->length; return true; } +#endif /* CONFIG_CODEC == SWCODEC */ static bool get_aiff_metadata(int fd, struct mp3entry* id3) { @@ -1318,7 +1322,7 @@ static bool get_aiff_metadata(int fd, struct mp3entry* id3) } /* Simple file type probing by looking at the filename extension. */ -static unsigned int probe_file_format(const char *filename) +unsigned int probe_file_format(const char *filename) { char *suffix; unsigned int i; @@ -1349,9 +1353,11 @@ static unsigned int probe_file_format(const char *filename) bool get_metadata(struct track_info* track, int fd, const char* trackname, bool v1first) { +#if CONFIG_CODEC == SWCODEC unsigned char* buf; unsigned long totalsamples; int i; +#endif /* Take our best guess at the codec type based on file extension */ track->id3.codectype = probe_file_format(trackname); @@ -1369,6 +1375,7 @@ bool get_metadata(struct track_info* track, int fd, const char* trackname, break; +#if CONFIG_CODEC == SWCODEC case AFMT_FLAC: if (!get_flac_metadata(fd, &(track->id3))) { @@ -1519,6 +1526,7 @@ bool get_metadata(struct track_info* track, int fd, const char* trackname, } /* TODO: read the id3v2 header if it exists */ break; +#endif /* CONFIG_CODEC == SWCODEC */ case AFMT_AIFF: if (!get_aiff_metadata(fd, &(track->id3))) @@ -1543,3 +1551,4 @@ bool get_metadata(struct track_info* track, int fd, const char* trackname, return true; } + diff --git a/apps/metadata.h b/apps/metadata.h index 1e07a18277..f790146041 100644 --- a/apps/metadata.h +++ b/apps/metadata.h @@ -22,6 +22,7 @@ #include "playback.h" +unsigned int probe_file_format(const char *filename); bool get_metadata(struct track_info* track, int fd, const char* trackname, bool v1first); diff --git a/apps/onplay.c b/apps/onplay.c index 2beb374956..5dcbe6e13e 100644 --- a/apps/onplay.c +++ b/apps/onplay.c @@ -820,12 +820,12 @@ int onplay(char* file, int attr, int from) items[i].desc = ID2P(LANG_MENU_SHOW_ID3_INFO); items[i].function = browse_id3; i++; - if(rundb_initialized) +/* if(rundb_initialized) { items[i].desc = ID2P(LANG_MENU_SET_RATING); items[i].function = set_rating; i++; - } + }*/ } #ifdef HAVE_MULTIVOLUME diff --git a/apps/plugin.c b/apps/plugin.c index 4395195d0f..51e071020a 100644 --- a/apps/plugin.c +++ b/apps/plugin.c @@ -333,16 +333,6 @@ static const struct plugin_api rockbox_api = { #endif #endif /* !SIMULATOR && CONFIG_CODEC != SWCODEC */ - /* tag database */ - &tagdbheader, - &tagdb_fd, - &tagdb_initialized, - tagdb_init, - /* runtime database */ - &rundbheader, - &rundb_fd, - &rundb_initialized, - /* menu */ menu_init, menu_exit, diff --git a/apps/plugin.h b/apps/plugin.h index 6584dbeed7..99dd3dc6ea 100644 --- a/apps/plugin.h +++ b/apps/plugin.h @@ -99,12 +99,12 @@ #define PLUGIN_MAGIC 0x526F634B /* RocK */ /* increase this every time the api struct changes */ -#define PLUGIN_API_VERSION 13 +#define PLUGIN_API_VERSION 14 /* update this to latest version if a change to the api struct breaks backwards compatibility (and please take the opportunity to sort in any new function which are "waiting" at the end of the function table) */ -#define PLUGIN_MIN_API_VERSION 13 +#define PLUGIN_MIN_API_VERSION 14 /* plugin return codes */ enum plugin_status { @@ -387,16 +387,6 @@ struct plugin_api { #endif #endif - /* tag database */ - struct tagdb_header *tagdbheader; - int *tagdb_fd; - int *tagdb_initialized; - int (*tagdb_init) (void); - /* runtime database */ - struct rundb_header *rundbheader; - int *rundb_fd; - int *rundb_initialized; - /* menu */ int (*menu_init)(const struct menu_item* mitems, int count, int (*callback)(int, int), diff --git a/apps/plugins/Makefile b/apps/plugins/Makefile index 54917ab658..586eb605da 100644 --- a/apps/plugins/Makefile +++ b/apps/plugins/Makefile @@ -60,7 +60,8 @@ DIRS = . # Subdirs containing multi-file plugins #for all targets -SUBDIRS += searchengine databox +# SUBDIRS += searchengine databox +SUBDIRS += databox #for any recorder, iRiver or iPod model ifneq (,$(strip $(foreach tgt,RECORDER IRIVER IPOD_COLOR IPOD_VIDEO GIGABEAT,$(findstring $(tgt),$(TARGET))))) diff --git a/apps/plugins/SOURCES b/apps/plugins/SOURCES index 5ba983e4f7..68d7929b81 100644 --- a/apps/plugins/SOURCES +++ b/apps/plugins/SOURCES @@ -15,7 +15,7 @@ metronome.c #endif mosaique.c rockbox_flash.c -search.c +/* search.c */ snow.c sort.c stats.c diff --git a/apps/settings.c b/apps/settings.c index 8c4e3dea71..6fe9665086 100644 --- a/apps/settings.c +++ b/apps/settings.c @@ -565,6 +565,11 @@ static const struct bit_entry hd_bits[] = {LCD_DEPTH,S_O(fg_color),LCD_DEFAULT_FG,"foreground color","rgb"}, {LCD_DEPTH,S_O(bg_color),LCD_DEFAULT_BG,"background color","rgb"}, #endif + +#ifdef HAVE_DIRCACHE + {1, S_O(tagcache_ram), 0, "tagcache_ram", off_on }, +#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 424341f340..fb8e11ffde 100644 --- a/apps/settings.h +++ b/apps/settings.h @@ -419,6 +419,7 @@ struct user_settings #ifdef HAVE_DIRCACHE bool dircache; /* enable directory cache */ int dircache_size; /* directory cache structure last size, 22 bits */ + bool tagcache_ram; /* tag cache mode (0=disabled,1=disk,2=ram) */ #endif int default_codepage; /* set default codepage for tag conversion */ #ifdef HAVE_REMOTE_LCD diff --git a/apps/settings_menu.c b/apps/settings_menu.c index ab0e21d098..e381a14595 100644 --- a/apps/settings_menu.c +++ b/apps/settings_menu.c @@ -49,6 +49,7 @@ #include "database.h" #include "dir.h" #include "dircache.h" +#include "tagcache.h" #include "rbunicode.h" #include "splash.h" #include "yesno.h" @@ -1259,15 +1260,15 @@ static bool next_folder(void) static bool runtimedb(void) { bool rc; - bool old = global_settings.runtimedb; +// bool old = global_settings.runtimedb; rc = set_bool( str(LANG_RUNTIMEDB_ACTIVE), &global_settings.runtimedb ); - if (old && !global_settings.runtimedb) +/* if (old && !global_settings.runtimedb) rundb_shutdown(); if (!old && global_settings.runtimedb) rundb_init(); - +*/ return rc; } @@ -1479,6 +1480,7 @@ static bool beep(void) } #endif + #ifdef HAVE_DIRCACHE static bool dircache(void) { @@ -1497,6 +1499,16 @@ static bool dircache(void) return result; } +static bool tagcache_ram(void) +{ + bool result = set_bool_options(str(LANG_TAGCACHE), + &global_settings.tagcache_ram, + STR(LANG_TAGCACHE_RAM), + STR(LANG_TAGCACHE_DISK), + NULL); + + return result; +} #endif /* HAVE_DIRCACHE */ static bool playback_settings_menu(void) @@ -1523,6 +1535,10 @@ static bool playback_settings_menu(void) #endif { ID2P(LANG_ID3_ORDER), id3_order }, { ID2P(LANG_NEXT_FOLDER), next_folder }, +#ifdef HAVE_DIRCACHE + { ID2P(LANG_TAGCACHE), tagcache_ram }, +#endif + { ID2P(LANG_TAGCACHE_FORCE_UPDATE), tagcache_force_update }, { ID2P(LANG_RUNTIMEDB_ACTIVE), runtimedb }, }; diff --git a/apps/tagcache.c b/apps/tagcache.c new file mode 100644 index 0000000000..6c6dd00a60 --- /dev/null +++ b/apps/tagcache.c @@ -0,0 +1,1868 @@ +/*************************************************************************** + * __________ __ ___. + * 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. + * + ****************************************************************************/ + +#include +#include "thread.h" +#include "kernel.h" +#include "system.h" +#include "logf.h" +#include "string.h" +#include "usb.h" +#include "dircache.h" +#include "metadata.h" +#include "id3.h" +#include "settings.h" +#include "splash.h" +#include "lang.h" +#include "tagcache.h" + +/* External reference to the big audiobuffer. */ +extern char *audiobuf, *audiobufend; + +/* Tag Cache thread. */ +static struct event_queue tagcache_queue; +static long tagcache_stack[(DEFAULT_STACK_SIZE + 0x4000)/sizeof(long)]; +static const char tagcache_thread_name[] = "tagcache"; + +/* Previous path when scanning directory tree recursively. */ +static char curpath[MAX_PATH*2]; +static long curpath_size = sizeof(curpath); + +/* Used when removing duplicates. */ +static char *tempbuf; /* Allocated when needed. */ +static long tempbufidx; /* Current location in buffer. */ +static long tempbuf_size; /* Buffer size (TEMPBUF_SIZE). */ +static long tempbuf_left; /* Buffer space left. */ +static long tempbuf_pos; + +/* Tags we want to be unique (loaded to the tempbuf). */ +static const int unique_tags[] = { tag_artist, tag_album, tag_genre, tag_title }; + +/* Queue commands. */ +#define Q_STOP_SCAN 0 +#define Q_START_SCAN 1 +#define Q_FORCE_UPDATE 2 + +/* Tag database files. */ +#define TAGCACHE_FILE_TEMP ROCKBOX_DIR "/tagcache_tmp.tcd" +#define TAGCACHE_FILE_MASTER ROCKBOX_DIR "/tagcache_idx.tcd" +#define TAGCACHE_FILE_INDEX ROCKBOX_DIR "/tagcache_%d.tcd" + +/* Tag database structures. */ +#define TAGCACHE_MAGIC 0x01020316 + +/* Variable-length tag entry in tag files. */ +struct tagfile_entry { + short tag_length; + char tag_data[0]; +}; + +/* Fixed-size tag entry in master db index. */ +struct index_entry { + long tag_seek[TAG_COUNT]; +}; + +/* Header is the same in every file. */ +struct tagcache_header { + long magic; + long datasize; + long entry_count; +}; + +#ifdef HAVE_TC_RAMCACHE +/* Header is created when loading database to ram. */ +struct ramcache_header { + struct tagcache_header h; + struct index_entry *indices; + char *tags[TAG_COUNT]; + int entry_count[TAG_COUNT]; +}; + +static struct ramcache_header *hdr; +static bool ramcache = false; +static long tagcache_size = 0; +#endif + +/** + * Full tag entries stored in a temporary file waiting + * for commit to the cache. */ +struct temp_file_entry { + long tag_offset[TAG_COUNT]; + short tag_length[TAG_COUNT]; + + long data_length; +}; + +struct tempbuf_id { + int id; + struct tempbuf_id *next; +}; + +struct tempbuf_searchidx { + struct tempbuf_id *id; + char *str; + int seek; +}; + + +/* Used when building the temporary file. */ +static int cachefd = -1, filenametag_fd; +static int total_entry_count = 0; +static int data_size = 0; +static int processed_dir_count; + +#ifdef HAVE_TC_RAMCACHE +static struct index_entry *find_entry_ram(const char *filename, + const struct dircache_entry *dc) +{ + static long last_pos = 0; + int counter = 0; + int i; + + /* Check if we tagcache is loaded into ram. */ + if (!ramcache) + return NULL; + + if (dc == NULL) + dc = dircache_get_entry_ptr(filename); + + if (dc == NULL) + { + logf("tagcache: file not found."); + return NULL; + } + + try_again: + + if (last_pos > 0) + i = last_pos; + else + i = 0; + + for (; i < hdr->h.entry_count - last_pos; i++) + { + if (hdr->indices[i].tag_seek[tag_filename] == (long)dc) + { + last_pos = MAX(0, i - 3); + return &hdr->indices[i]; + } + + if (++counter == 100) + { + yield(); + counter = 0; + } + } + + if (last_pos > 0) + { + last_pos = 0; + goto try_again; + } + + return NULL; +} +#endif + +static struct index_entry *find_entry_disk(const char *filename, bool retrieve) +{ + static struct index_entry idx; + static long last_pos = -1; + long pos_history[POS_HISTORY_COUNT]; + long pos_history_idx = 0; + struct tagcache_header tch; + bool found = false; + struct tagfile_entry tfe; + int masterfd, fd = filenametag_fd; + char buf[MAX_PATH]; + int i; + int pos = -1; + + if (fd < 0) + { + last_pos = -1; + return NULL; + } + + check_again: + + if (last_pos > 0) + lseek(fd, last_pos, SEEK_SET); + else + lseek(fd, sizeof(struct tagcache_header), SEEK_SET); + + while (true) + { + pos = lseek(fd, 0, SEEK_CUR); + for (i = pos_history_idx-1; i >= 0; i--) + pos_history[i+1] = pos_history[i]; + pos_history[0] = pos; + + if (read(fd, &tfe, sizeof(struct tagfile_entry)) != + sizeof(struct tagfile_entry)) + { + break ; + } + + if (tfe.tag_length >= (long)sizeof(buf)) + { + logf("too long tag"); + close(fd); + last_pos = -1; + return NULL; + } + + if (read(fd, buf, tfe.tag_length) != tfe.tag_length) + { + logf("read error #2"); + close(fd); + last_pos = -1; + return NULL; + } + + if (!strcasecmp(filename, buf)) + { + last_pos = pos_history[pos_history_idx]; + found = true; + break ; + } + + if (pos_history_idx < POS_HISTORY_COUNT - 1) + pos_history_idx++; + } + + /* Not found? */ + if (!found) + { + if (last_pos > 0) + { + last_pos = -1; + logf("seek again"); + goto check_again; + } + //close(fd); + return NULL; + } + + if (!retrieve) + { + /* Just return a valid pointer without a valid entry. */ + return &idx; + } + + /* Found. Now read the index_entry (if requested). */ + masterfd = open(TAGCACHE_FILE_MASTER, O_RDONLY); + if (masterfd < 0) + { + logf("open fail"); + return NULL; + } + + if (read(fd, &tch, sizeof(struct tagcache_header)) != + sizeof(struct tagcache_header) || tch.magic != TAGCACHE_MAGIC) + { + logf("header error"); + return NULL; + } + + for (i = 0; i < tch.entry_count; i++) + { + if (read(masterfd, &idx, sizeof(struct index_entry)) != + sizeof(struct index_entry)) + { + logf("read error #3"); + close(fd); + return NULL; + } + + if (idx.tag_seek[tag_filename] == pos) + break ; + } + close(masterfd); + + /* Not found? */ + if (i == tch.entry_count) + { + logf("not found!"); + return NULL; + } + + return &idx; +} + +static bool build_lookup_list(struct tagcache_search *tcs) +{ + struct tagcache_header header; + struct index_entry entry; + int masterfd; + int i; + + tcs->seek_list_count = 0; + +#ifdef HAVE_TC_RAMCACHE + if (tcs->ramsearch) + { + int j; + + for (i = tcs->seek_pos; i < hdr->h.entry_count - tcs->seek_pos; i++) + { + if (tcs->seek_list_count == SEEK_LIST_SIZE) + break ; + + for (j = 0; j < tcs->filter_count; j++) + { + if (hdr->indices[i].tag_seek[tcs->filter_tag[j]] != + tcs->filter_seek[j]) + break ; + } + + if (j < tcs->filter_count) + continue ; + + /* Add to the seek list if not already there. */ + for (j = 0; j < tcs->seek_list_count; j++) + { + if (tcs->seek_list[j] == hdr->indices[i].tag_seek[tcs->type]) + break ; + } + + /* Lets add it. */ + if (j == tcs->seek_list_count) + { + tcs->seek_list[tcs->seek_list_count] = + hdr->indices[i].tag_seek[tcs->type]; + tcs->seek_list_count++; + } + } + + tcs->seek_pos = i; + + return tcs->seek_list_count > 0; + } +#endif + + masterfd = open(TAGCACHE_FILE_MASTER, O_RDONLY); + if (masterfd < 0) + { + logf("cannot open master index"); + return false; + } + + /* Load the header. */ + if (read(masterfd, &header, sizeof(struct tagcache_header)) != + sizeof(struct tagcache_header) || header.magic != TAGCACHE_MAGIC) + { + logf("read error"); + close(masterfd); + return false; + } + + lseek(masterfd, tcs->seek_pos * sizeof(struct index_entry) + + sizeof(struct tagcache_header), SEEK_SET); + + while (read(masterfd, &entry, sizeof(struct index_entry)) == + sizeof(struct index_entry)) + { + if (tcs->seek_list_count == SEEK_LIST_SIZE) + break ; + + for (i = 0; i < tcs->filter_count; i++) + { + if (entry.tag_seek[tcs->filter_tag[i]] != tcs->filter_seek[i]) + break ; + } + + tcs->seek_pos++; + + if (i < tcs->filter_count) + continue ; + + /* Add to the seek list if not already there. */ + for (i = 0; i < tcs->seek_list_count; i++) + { + if (tcs->seek_list[i] == entry.tag_seek[tcs->type]) + break ; + } + + /* Lets add it. */ + if (i == tcs->seek_list_count) + { + tcs->seek_list[tcs->seek_list_count] = + entry.tag_seek[tcs->type]; + tcs->seek_list_count++; + } + + } + close(masterfd); + + return tcs->seek_list_count > 0; +} + +bool tagcache_search(struct tagcache_search *tcs, int tag) +{ + struct tagcache_header h; + char buf[MAX_PATH]; + + if (tcs->valid) + tagcache_search_finish(tcs); + + tcs->position = sizeof(struct tagcache_header); + tcs->fd = -1; + tcs->type = tag; + tcs->seek_pos = 0; + tcs->seek_list_count = 0; + tcs->filter_count = 0; + tcs->valid = true; + +#ifndef HAVE_TC_RAMCACHE + tcs->ramsearch = false; +#else + tcs->ramsearch = ramcache; + if (tcs->ramsearch) + { + tcs->entry_count = hdr->entry_count[tcs->type]; + } + else +#endif + { + snprintf(buf, sizeof buf, TAGCACHE_FILE_INDEX, tcs->type); + tcs->fd = open(buf, O_RDONLY); + if (tcs->fd < 0) + { + logf("failed to open index"); + return false; + } + + /* Check the header. */ + if (read(tcs->fd, &h, sizeof(struct tagcache_header)) != + sizeof(struct tagcache_header) || h.magic != TAGCACHE_MAGIC) + { + logf("incorrect header"); + return false; + } + } + + return true; +} + +bool tagcache_search_add_filter(struct tagcache_search *tcs, + int tag, int seek) +{ + if (tcs->filter_count == TAGCACHE_MAX_FILTERS) + return false; + + tcs->filter_tag[tcs->filter_count] = tag; + tcs->filter_seek[tcs->filter_count] = seek; + tcs->filter_count++; + + return true; +} + +bool tagcache_get_next(struct tagcache_search *tcs) +{ + static char buf[MAX_PATH]; + struct tagfile_entry entry; + + if (!tcs->valid) + return false; + + if (tcs->fd < 0 +#ifdef HAVE_TC_RAMCACHE + && !tcs->ramsearch +#endif + ) + return false; + + /* Relative fetch. */ + if (tcs->filter_count > 0) + { + /* Check for end of list. */ + if (tcs->seek_list_count == 0) + { + /* Try to fetch more. */ + if (!build_lookup_list(tcs)) + return false; + } + + tcs->seek_list_count--; + + /* Seek stream to the correct position and continue to direct fetch. */ + if (!tcs->ramsearch) + lseek(tcs->fd, tcs->seek_list[tcs->seek_list_count], SEEK_SET); + else + tcs->position = tcs->seek_list[tcs->seek_list_count]; + } + + /* Direct fetch. */ +#ifdef HAVE_TC_RAMCACHE + if (tcs->ramsearch) + { + struct tagfile_entry *ep; + + if (tcs->entry_count == 0) + { + tcs->valid = false; + return false; + } + tcs->entry_count--; + tcs->result_seek = tcs->position; + + if (tcs->type == tag_filename) + { + dircache_copy_path((struct dircache_entry *)tcs->position, + buf, sizeof buf); + tcs->result = buf; + tcs->result_len = strlen(buf) + 1; + + return true; + } + + ep = (struct tagfile_entry *)&hdr->tags[tcs->type][tcs->position]; + tcs->position += sizeof(struct tagfile_entry) + ep->tag_length; + tcs->result = ep->tag_data; + tcs->result_len = ep->tag_length; + + return true; + } + else +#endif + { + tcs->result_seek = lseek(tcs->fd, 0, SEEK_CUR); + if (read(tcs->fd, &entry, sizeof(struct tagfile_entry)) != + sizeof(struct tagfile_entry)) + { + /* End of data. */ + tcs->valid = false; + return false; + } + } + + if (entry.tag_length > (long)sizeof(buf)) + { + tcs->valid = false; + logf("too long tag"); + return false; + } + + if (read(tcs->fd, buf, entry.tag_length) != entry.tag_length) + { + tcs->valid = false; + logf("read error"); + return false; + } + + tcs->result = buf; + tcs->result_len = entry.tag_length; + + return true; +} + +#if 0 +void tagcache_modify(struct tagcache_search *tcs, int type, const char *text) +{ + struct tagentry *entry; + + if (tcs->type != tag_title) + return ; + + /* We will need reserve buffer for this. */ + if (tcs->ramcache) + { + struct tagfile_entry *ep; + + ep = (struct tagfile_entry *)&hdr->tags[tcs->type][tcs->result_seek]; + tcs->seek_list[tcs->seek_list_count]; + } + + entry = find_entry_ram( + +} +#endif + +void tagcache_search_finish(struct tagcache_search *tcs) +{ + if (tcs->fd >= 0) + { + close(tcs->fd); + tcs->fd = -1; + tcs->ramsearch = false; + tcs->valid = false; + } +} + +#ifdef HAVE_TC_RAMCACHE +struct tagfile_entry *get_tag(struct index_entry *entry, int tag) +{ + return (struct tagfile_entry *)&hdr->tags[tag][entry->tag_seek[tag]]; +} + +bool tagcache_fill_tags(struct mp3entry *id3, const char *filename) +{ + struct index_entry *entry; + + /* Find the corresponding entry in tagcache. */ + entry = find_entry_ram(filename, NULL); + if (entry == NULL || !ramcache) + return false; + + id3->title = get_tag(entry, tag_title)->tag_data; + id3->artist = get_tag(entry, tag_artist)->tag_data; + id3->album = get_tag(entry, tag_album)->tag_data; + id3->genre_string = get_tag(entry, tag_genre)->tag_data; + + return true; +} +#endif + +static inline void write_item(const char *item) +{ + int len = strlen(item) + 1; + + data_size += len; + write(cachefd, item, len); +} + +inline void check_if_empty(char **tag) +{ + if (tag == NULL || *tag == NULL || *tag[0] == '\0') + *tag = "Unknown"; +} + +#define CRC_BUF_LEN 8 + +#ifdef HAVE_TC_RAMCACHE +static void add_tagcache(const char *path, const struct dircache_entry *dc) +#else +static void add_tagcache(const char *path) +#endif +{ + struct track_info track; + struct temp_file_entry entry; + bool ret; + int fd; + //uint32_t crcbuf[CRC_BUF_LEN]; + + if (cachefd < 0) + return ; + + /* Check if the file is supported. */ + if (probe_file_format(path) == AFMT_UNKNOWN) + return ; + + /* Check if the file is already cached. */ +#ifdef HAVE_TC_RAMCACHE + if (ramcache) + { + if (find_entry_ram(path, dc)) + return ; + } + else +#endif + { + if (find_entry_disk(path, false)) + return ; + } + + fd = open(path, O_RDONLY); + if (fd < 0) + { + logf("open fail: %s", path); + return ; + } + + memset(&track, 0, sizeof(struct track_info)); + ret = get_metadata(&track, fd, path, false); + close(fd); + + if (!ret) + { + track.id3.title = (char *)path; + track.id3.artist = "Unknown"; + track.id3.album = "Unknown"; + track.id3.genre_string = "Unknown"; + } + else + check_if_empty(&track.id3.title); + check_if_empty(&track.id3.artist); + check_if_empty(&track.id3.album); + check_if_empty(&track.id3.genre_string); + + entry.tag_length[tag_filename] = strlen(path) + 1; + entry.tag_length[tag_title] = strlen(track.id3.title) + 1; + entry.tag_length[tag_artist] = strlen(track.id3.artist) + 1; + entry.tag_length[tag_album] = strlen(track.id3.album) + 1; + entry.tag_length[tag_genre] = strlen(track.id3.genre_string) + 1; + + entry.tag_offset[tag_filename] = 0; + entry.tag_offset[tag_title] = entry.tag_offset[tag_filename] + entry.tag_length[tag_filename]; + entry.tag_offset[tag_artist] = entry.tag_offset[tag_title] + entry.tag_length[tag_title]; + entry.tag_offset[tag_album] = entry.tag_offset[tag_artist] + entry.tag_length[tag_artist]; + entry.tag_offset[tag_genre] = entry.tag_offset[tag_album] + entry.tag_length[tag_album]; + entry.data_length = entry.tag_offset[tag_genre] + entry.tag_length[tag_genre]; + + write(cachefd, &entry, sizeof(struct temp_file_entry)); + write_item(path); + write_item(track.id3.title); + write_item(track.id3.artist); + write_item(track.id3.album); + write_item(track.id3.genre_string); + total_entry_count++; +} + +static void remove_files(void) +{ + int i; + char buf[MAX_PATH]; + + remove(TAGCACHE_FILE_MASTER); + for (i = 0; i < TAG_COUNT; i++) + { + snprintf(buf, sizeof buf, TAGCACHE_FILE_INDEX, i); + remove(buf); + } +} + +static bool tempbuf_insert(char *str, int id) +{ + struct tempbuf_searchidx *index = (struct tempbuf_searchidx *)tempbuf; + int len = strlen(str)+1; + + /* Insert it to the buffer. */ + tempbuf_left -= len + sizeof(struct tempbuf_id); + if (tempbuf_left - 4 < 0 || tempbufidx >= TAGFILE_MAX_ENTRIES-1) + return false; + + index[tempbufidx].id = (struct tempbuf_id *)&tempbuf[tempbuf_pos]; +#ifdef ROCKBOX_STRICT_ALIGN + /* Make sure the entry is long aligned. */ + if ((long)index[tempbufidx].id & 0x03) + { + int fix = 4 - ((long)id & 0x03); + tempbuf_left -= fix; + tempbuf_pos += fix; + index[tempbufidx].id = (struct tempbuf_id *)(( + (long)index[tempbufidx].id & ~0x03) + 0x04); + } +#endif + index[tempbufidx].id->id = id; + index[tempbufidx].id->next = NULL; + tempbuf_pos += sizeof(struct tempbuf_id); + + index[tempbufidx].seek = -1; + index[tempbufidx].str = &tempbuf[tempbuf_pos]; + memcpy(index[tempbufidx].str, str, len); + tempbuf_pos += len; + tempbufidx++; + + return true; +} + +static bool tempbuf_unique_insert(char *str, int id) +{ + struct tempbuf_searchidx *index = (struct tempbuf_searchidx *)tempbuf; + struct tempbuf_id *idp; + int i; + + /* Check if string already exists. */ + for (i = 0; i < tempbufidx; i++) + { + if (!strcasecmp(str, index[i].str)) + { + tempbuf_left -= sizeof(struct tempbuf_id); + if (tempbuf_left < 0) + return false; + + idp = index[i].id; + while (idp->next != NULL) + idp = idp->next; + + idp->next = (struct tempbuf_id *)&tempbuf[tempbuf_pos]; + idp = idp->next; + idp->id = id; + idp->next = NULL; + tempbuf_pos += sizeof(struct tempbuf_id); + + return true; + } + } + + return tempbuf_insert(str, id); +} + +static int compare(const void *p1, const void *p2) +{ + struct tempbuf_searchidx *e1 = (struct tempbuf_searchidx *)p1; + struct tempbuf_searchidx *e2 = (struct tempbuf_searchidx *)p2; + + /* + if (!strncasecmp("the ", e1, 4)) + e1 = &e1[4]; + if (!strncasecmp("the ", e2, 4)) + e2 = &e2[4]; + */ + + return strncasecmp(e1->str, e2->str, MAX_PATH); +} + +static int tempbuf_sort(int fd) +{ + struct tempbuf_searchidx *index = (struct tempbuf_searchidx *)tempbuf; + struct tagfile_entry fe; + int i; + + qsort(index, tempbufidx, sizeof(struct tempbuf_searchidx), compare); + + for (i = 0; i < tempbufidx; i++) + { + index[i].seek = lseek(fd, 0, SEEK_CUR); + fe.tag_length = strlen(index[i].str) + 1; + if (write(fd, &fe, sizeof(struct tagfile_entry)) != + sizeof(struct tagfile_entry)) + { + logf("tempbuf_sort: write error #1"); + return -1; + } + + if (write(fd, index[i].str, fe.tag_length) != + fe.tag_length) + { + logf("tempbuf_sort: write error #2"); + return -2; + } + } + + return i; +} + + +static struct tempbuf_searchidx* tempbuf_locate(int id) +{ + struct tempbuf_searchidx *index = (struct tempbuf_searchidx *)tempbuf; + struct tempbuf_id *idp; + int i; + + /* Check if string already exists. */ + for (i = 0; i < tempbufidx; i++) + { + idp = index[i].id; + while (idp != NULL) + { + if (idp->id == id) + return &index[i]; + idp = idp->next; + } + } + + return NULL; +} + + +static int tempbuf_find_location(int id) +{ + struct tempbuf_searchidx *entry; + + entry = tempbuf_locate(id); + if (entry == NULL) + return -1; + + return entry->seek; +} + +static bool is_unique_tag(int type) +{ + int i; + + for (i = 0; i < (int)(sizeof(unique_tags)/sizeof(unique_tags[0])); i++) + { + if (type == unique_tags[i]) + return true; + } + + return false; +} + +static bool build_index(int index_type, struct tagcache_header *h, int tmpfd) +{ + int i; + struct tagcache_header tch; + struct index_entry idx; + char buf[MAX_PATH]; + int fd = -1, masterfd; + bool error = false; + int init; + int masterfd_pos; + + logf("Building index: %d", index_type); + + tempbufidx = 0; + tempbuf_pos = TAGFILE_MAX_ENTRIES * sizeof(struct tempbuf_searchidx); + tempbuf_left = tempbuf_size - tempbuf_pos; + if (tempbuf_left < 0) + { + logf("Buffer way too small!"); + return false; + } + + /* Open the index file, which contains the tag names. */ + snprintf(buf, sizeof buf, TAGCACHE_FILE_INDEX, index_type); + fd = open(buf, O_RDWR); + + if (fd >= 0) + { + /* Read the header. */ + if (read(fd, &tch, sizeof(struct tagcache_header)) != + sizeof(struct tagcache_header) || tch.magic != TAGCACHE_MAGIC) + { + logf("header error"); + close(fd); + return false; + } + + /** + * If tag file contains unique tags (sorted index), we will load + * it entirely into memory so we can resort it later for use with + * chunked browsing. + */ + if (is_unique_tag(index_type)) + { + for (i = 0; i < tch.entry_count; i++) + { + struct tagfile_entry entry; + int loc = lseek(fd, 0, SEEK_CUR); + + if (read(fd, &entry, sizeof(struct tagfile_entry)) + != sizeof(struct tagfile_entry)) + { + logf("read error"); + close(fd); + return false; + } + + if (entry.tag_length >= (int)sizeof(buf)) + { + logf("too long tag"); + close(fd); + return false; + } + + if (read(fd, buf, entry.tag_length) != entry.tag_length) + { + logf("read error #2"); + close(fd); + return false; + } + + /** + * Save the tag and tag id in the memory buffer. Tag id + * is saved so we can later reindex the master lookup + * table when the index gets resorted. + */ + tempbuf_insert(buf, loc + TAGFILE_MAX_ENTRIES); + } + } + else + tempbufidx = tch.entry_count; + } + else + { + /** + * Creating new index file to store the tags. No need to preload + * anything whether the index type is sorted or not. + */ + fd = open(buf, O_WRONLY | O_CREAT | O_TRUNC); + if (fd < 0) + { + logf("%s open fail", buf); + return false; + } + + tch.magic = TAGCACHE_MAGIC; + tch.entry_count = 0; + tch.datasize = 0; + + if (write(fd, &tch, sizeof(struct tagcache_header)) != + sizeof(struct tagcache_header)) + { + logf("header write failed"); + close(fd); + return false; + } + } + + /* Loading the tag lookup file as "master file". */ + logf("Loading index file"); + masterfd = open(TAGCACHE_FILE_MASTER, O_RDWR); + + if (masterfd < 0) + { + logf("Creating new index"); + masterfd = open(TAGCACHE_FILE_MASTER, O_WRONLY | O_CREAT | O_TRUNC); + + if (masterfd < 0) + { + logf("Failure to create index file"); + close(fd); + return false; + } + + /* Write the header (write real values later). */ + tch = *h; + tch.entry_count = 0; + tch.datasize = 0; + write(masterfd, &tch, sizeof(struct tagcache_header)); + init = true; + masterfd_pos = lseek(masterfd, 0, SEEK_CUR); + } + else + { + /** + * Master file already exists so we need to process the current + * file first. + */ + init = false; + + if (read(masterfd, &tch, sizeof(struct tagcache_header)) != + sizeof(struct tagcache_header) || tch.magic != TAGCACHE_MAGIC) + { + logf("header error"); + close(fd); + close(masterfd); + return false; + } + + /** + * If we reach end of the master file, we need to expand it to + * hold new tags. If the current index is not sorted, we can + * simply append new data to end of the file. + * However, if the index is sorted, we need to update all tag + * pointers in the master file for the current index. + */ + masterfd_pos = lseek(masterfd, tch.entry_count * sizeof(struct index_entry), + SEEK_CUR); + if (masterfd_pos == filesize(masterfd)) + { + logf("appending..."); + init = true; + } + } + + /** + * Load new unique tags in memory to be sorted later and added + * to the master lookup file. + */ + if (is_unique_tag(index_type)) + { + lseek(tmpfd, sizeof(struct tagcache_header), SEEK_SET); + /* h is the header of the temporary file containing new tags. */ + for (i = 0; i < h->entry_count; i++) + { + struct temp_file_entry entry; + + if (read(tmpfd, &entry, sizeof(struct temp_file_entry)) != + sizeof(struct temp_file_entry)) + { + logf("read fail #1"); + error = true; + goto error_exit; + } + + /* Read data. */ + if (entry.tag_length[index_type] >= (long)sizeof(buf)) + { + logf("too long entry!"); + error = true; + goto error_exit; + } + + lseek(tmpfd, entry.tag_offset[index_type], SEEK_CUR); + if (read(tmpfd, buf, entry.tag_length[index_type]) != + entry.tag_length[index_type]) + { + logf("read fail #3"); + error = true; + goto error_exit; + } + + if (!tempbuf_unique_insert(buf, i)) + { + logf("insert error"); + error = true; + goto error_exit; + } + + /* Skip to next. */ + lseek(tmpfd, entry.data_length - entry.tag_offset[index_type] - + entry.tag_length[index_type], SEEK_CUR); + } + + /* Sort the buffer data and write it to the index file. */ + lseek(fd, sizeof(struct tagcache_header), SEEK_SET); + i = tempbuf_sort(fd); + if (i < 0) + goto error_exit; + logf("sorted %d tags", i); + + /** + * Now update all indexes in the master lookup file. + */ + lseek(masterfd, sizeof(struct tagcache_header), SEEK_SET); + for (i = 0; i < tch.entry_count; i++) + { + int loc = lseek(masterfd, 0, SEEK_CUR); + + if (read(masterfd, &idx, sizeof(struct index_entry)) != + sizeof(struct index_entry)) + { + logf("read fail #2"); + error = true; + goto error_exit ; + } + idx.tag_seek[index_type] = tempbuf_find_location( + idx.tag_seek[index_type]+TAGFILE_MAX_ENTRIES); + if (idx.tag_seek[index_type] < 0) + { + logf("update error: %d/%d", i, tch.entry_count); + error = true; + goto error_exit; + } + + /* Write back the updated index. */ + lseek(masterfd, loc, SEEK_SET); + if (write(masterfd, &idx, sizeof(struct index_entry)) != + sizeof(struct index_entry)) + { + logf("write fail"); + error = true; + goto error_exit; + } + } + } + + /** + * Walk through the temporary file containing the new tags. + */ + // build_normal_index(h, tmpfd, masterfd, idx); + lseek(masterfd, masterfd_pos, SEEK_SET); + lseek(tmpfd, sizeof(struct tagcache_header), SEEK_SET); + lseek(fd, 0, SEEK_END); + for (i = 0; i < h->entry_count; i++) + { + if (init) + { + memset(&idx, 0, sizeof(struct index_entry)); + } + else + { + if (read(masterfd, &idx, sizeof(struct index_entry)) != + sizeof(struct index_entry)) + { + logf("read fail #2"); + error = true; + break ; + } + lseek(masterfd, -sizeof(struct index_entry), SEEK_CUR); + } + + /* Read entry headers. */ + if (!is_unique_tag(index_type)) + { + struct temp_file_entry entry; + struct tagfile_entry fe; + + if (read(tmpfd, &entry, sizeof(struct temp_file_entry)) != + sizeof(struct temp_file_entry)) + { + logf("read fail #1"); + error = true; + break ; + } + + /* Read data. */ + if (entry.tag_length[index_type] >= (int)sizeof(buf)) + { + logf("too long entry!"); + logf("length=%d", entry.tag_length[index_type]); + logf("pos=0x%02x", lseek(tmpfd, 0, SEEK_CUR)); + error = true; + break ; + } + + lseek(tmpfd, entry.tag_offset[index_type], SEEK_CUR); + if (read(tmpfd, buf, entry.tag_length[index_type]) != + entry.tag_length[index_type]) + { + logf("read fail #3"); + logf("offset=0x%02x", entry.tag_offset[index_type]); + logf("length=0x%02x", entry.tag_length[index_type]); + error = true; + break ; + } + + /* Write to index file. */ + idx.tag_seek[index_type] = lseek(fd, 0, SEEK_CUR); + fe.tag_length = entry.tag_length[index_type]; + write(fd, &fe, sizeof(struct tagfile_entry)); + write(fd, buf, fe.tag_length); + tempbufidx++; + + /* Skip to next. */ + lseek(tmpfd, entry.data_length - entry.tag_offset[index_type] - + entry.tag_length[index_type], SEEK_CUR); + } + else + { + /* Locate the correct entry from the sorted array. */ + idx.tag_seek[index_type] = tempbuf_find_location(i); + if (idx.tag_seek[index_type] < 0) + { + logf("entry not found (%d)"); + error = true; + break ; + } + } + + + /* Write index. */ + if (write(masterfd, &idx, sizeof(struct index_entry)) != + sizeof(struct index_entry)) + { + logf("tagcache: write fail #4"); + error = true; + break ; + } + + } + + /* Finally write the uniqued tag index file. */ + if (is_unique_tag(index_type)) + { + tch.magic = TAGCACHE_MAGIC; + tch.entry_count = tempbufidx; + tch.datasize = lseek(fd, 0, SEEK_END) - sizeof(struct tagcache_header); + lseek(fd, 0, SEEK_SET); + write(fd, &tch, sizeof(struct tagcache_header)); + } + else + { + tch.magic = TAGCACHE_MAGIC; + tch.entry_count = tempbufidx; + tch.datasize = lseek(fd, 0, SEEK_CUR) - sizeof(struct tagcache_header); + lseek(fd, 0, SEEK_SET); + write(fd, &tch, sizeof(struct tagcache_header)); + } + + error_exit: + + close(fd); + close(masterfd); + + return !error; +} + +static bool commit(void) +{ + struct tagcache_header header, header_old; + int i, len, rc; + int tmpfd; + int masterfd; + + logf("committing tagcache"); + + tmpfd = open(TAGCACHE_FILE_TEMP, O_RDONLY); + if (tmpfd < 0) + { + logf("nothing to commit"); + return true; + } + + /* Load the header. */ + len = sizeof(struct tagcache_header); + rc = read(tmpfd, &header, len); + + if (header.magic != TAGCACHE_MAGIC || rc != len) + { + logf("incorrect header"); + close(tmpfd); + remove_files(); + remove(TAGCACHE_FILE_TEMP); + return false; + } + + if (header.entry_count == 0) + { + logf("nothing to commit"); + close(tmpfd); + remove(TAGCACHE_FILE_TEMP); + return true; + } + + if (tempbuf_size == 0) + { + logf("delaying commit until next boot"); + close(tmpfd); + return false; + } + + logf("commit %d entries...", header.entry_count); + + /* Now create the index files. */ + for (i = 0; i < TAG_COUNT; i++) + { + if (!build_index(i, &header, tmpfd)) + { + logf("tagcache failed init"); + remove_files(); + return false; + } + } + + close(tmpfd); + + /* Update the master index headers. */ + masterfd = open(TAGCACHE_FILE_MASTER, O_RDWR); + if (masterfd < 0) + { + logf("failed to open master index"); + return false; + } + + if (read(masterfd, &header_old, sizeof(struct tagcache_header)) + != sizeof(struct tagcache_header) || + header_old.magic != TAGCACHE_MAGIC) + { + logf("incorrect header"); + close(masterfd); + remove_files(); + return false; + } + + header.entry_count += header_old.entry_count; + header.datasize += header_old.datasize; + + lseek(masterfd, 0, SEEK_SET); + write(masterfd, &header, sizeof(struct tagcache_header)); + close(masterfd); + + logf("tagcache committed"); + remove(TAGCACHE_FILE_TEMP); + + return true; +} + +static void allocate_tempbuf(void) +{ + /* Yeah, malloc would be really nice now :) */ + tempbuf = (char *)(((long)audiobuf & ~0x03) + 0x04); + tempbuf_size = (int)audiobufend - (int)audiobuf - 4; + audiobuf += tempbuf_size; +} + +static void free_tempbuf(void) +{ + if (tempbuf_size == 0) + return ; + + audiobuf -= tempbuf_size; + tempbuf = NULL; + tempbuf_size = 0; +} + +#ifdef HAVE_TC_RAMCACHE +static bool allocate_tagcache(void) +{ + int rc, len; + int fd; + + hdr = NULL; + + fd = open(TAGCACHE_FILE_MASTER, O_RDONLY); + if (fd < 0) + { + logf("no tagcache file found."); + return false; + } + + /* Load the header. */ + hdr = (struct ramcache_header *)audiobuf; + memset(hdr, 0, sizeof(struct ramcache_header)); + len = sizeof(struct tagcache_header); + rc = read(fd, &hdr->h, len); + close(fd); + + if (hdr->h.magic != TAGCACHE_MAGIC || rc != len) + { + logf("incorrect header"); + remove_files(); + hdr = NULL; + return false; + } + + hdr->indices = (struct index_entry *)(hdr + 1); + + /* Now calculate the required cache size. */ + tagcache_size = hdr->h.datasize + + sizeof(struct index_entry) * hdr->h.entry_count + + sizeof(struct ramcache_header) + TAG_COUNT*sizeof(void *); + logf("tagcache: %d bytes allocated.", tagcache_size); + logf("at: 0x%04x", audiobuf); + audiobuf += (long)((tagcache_size & ~0x03) + 0x04); + + return true; +} + +static bool load_tagcache(void) +{ + struct tagcache_header *tch; + long bytesleft = tagcache_size; + struct index_entry *idx; + int rc, fd; + char *p; + int i; + + /* We really need the dircache for this. */ + while (!dircache_is_enabled()) + sleep(HZ); + + logf("loading tagcache to ram..."); + + fd = open(TAGCACHE_FILE_MASTER, O_RDONLY); + if (fd < 0) + { + logf("tagcache open failed"); + return false; + } + + lseek(fd, sizeof(struct tagcache_header), SEEK_SET); + + idx = hdr->indices; + + /* Load the master index table. */ + for (i = 0; i < hdr->h.entry_count; i++) + { + rc = read(fd, idx, sizeof(struct index_entry)); + if (rc != sizeof(struct index_entry)) + { + logf("read error #1"); + close(fd); + return false; + } + + bytesleft -= sizeof(struct index_entry); + if (bytesleft < 0 || ((long)idx - (long)hdr->indices) >= tagcache_size) + { + logf("too big tagcache."); + close(fd); + return false; + } + + idx++; + } + + close(fd); + + /* Load the tags. */ + p = (char *)idx; + for (i = 0; i < TAG_COUNT; i++) + { + struct tagfile_entry *fe; + char buf[MAX_PATH]; + + //p = ((void *)p+1); + p = (char *)((long)p & ~0x03) + 0x04; + hdr->tags[i] = p; + + snprintf(buf, sizeof buf, TAGCACHE_FILE_INDEX, i); + fd = open(buf, O_RDONLY); + + if (fd < 0) + { + logf("%s open fail", buf); + return false; + } + + /* Check the header. */ + tch = (struct tagcache_header *)p; + rc = read(fd, tch, sizeof(struct tagcache_header)); + p += rc; + if (rc != sizeof(struct tagcache_header) || + tch->magic != TAGCACHE_MAGIC) + { + logf("incorrect header"); + close(fd); + return false; + } + + for (hdr->entry_count[i] = 0; + hdr->entry_count[i] < tch->entry_count; + hdr->entry_count[i]++) + { + yield(); + fe = (struct tagfile_entry *)p; + rc = read(fd, fe, sizeof(struct tagfile_entry)); + if (rc != sizeof(struct tagfile_entry)) + { + /* End of lookup table. */ + logf("read error"); + close(fd); + return false; + } + + /* We have a special handling for the filename tags. */ + if (i == tag_filename) + { + const struct dircache_entry *dc; + + if (fe->tag_length >= (long)sizeof(buf)-1) + { + logf("too long filename"); + close(fd); + return false; + } + + rc = read(fd, buf, fe->tag_length); + if (rc != fe->tag_length) + { + logf("read error #3"); + close(fd); + return false; + } + + dc = dircache_get_entry_ptr(buf); + if (dc == NULL) + { + logf("Entry no longer valid."); + logf("-> %s", buf); + continue ; + } + + hdr->indices[hdr->entry_count[i]].tag_seek[tag_filename] + = (long)dc; + + continue ; + } + + bytesleft -= sizeof(struct tagfile_entry) + fe->tag_length; + if (bytesleft < 0) + { + logf("too big tagcache."); + close(fd); + return false; + } + + p = fe->tag_data; + rc = read(fd, fe->tag_data, fe->tag_length); + p += rc; + + if (rc != fe->tag_length) + { + logf("read error #4"); + logf("rc=0x%04x", rc); // 0x431 + logf("len=0x%04x", fe->tag_length); // 0x4000 + logf("pos=0x%04x", lseek(fd, 0, SEEK_CUR)); // 0x433 + logf("i=0x%02x", i); // 0x00 + close(fd); + return false; + } + } + close(fd); + } + + logf("tagcache loaded into ram!"); + + return true; +} +#endif + +static bool check_dir(const char *dirname) +{ + DIRCACHED *dir; + int len; + int success = false; + + dir = opendir_cached(dirname); + if (!dir) + { + logf("tagcache: opendir_cached() failed"); + return false; + } + + /* Recursively scan the dir. */ + while (queue_empty(&tagcache_queue)) + { + struct dircache_entry *entry; + + entry = readdir_cached(dir); + + if (entry == NULL) + { + success = true; + break ; + } + + if (!strcmp(entry->d_name, ".") || + !strcmp(entry->d_name, "..")) + continue; + + yield(); + + len = strlen(curpath); + snprintf(&curpath[len], curpath_size - len, "/%s", + entry->d_name); + + processed_dir_count++; + if (entry->attribute & ATTR_DIRECTORY) + check_dir(curpath); + else +#ifdef HAVE_TC_RAMCACHE + add_tagcache(curpath, dir->internal_entry); +#else + add_tagcache(curpath); +#endif + + curpath[len] = '\0'; + } + + closedir_cached(dir); + + return success; +} + +static void build_tagcache(void) +{ + struct tagcache_header header; + bool ret; + char buf[MAX_PATH]; + + curpath[0] = '\0'; + data_size = 0; + total_entry_count = 0; + processed_dir_count = 0; + + logf("updating tagcache"); + + cachefd = open(TAGCACHE_FILE_TEMP, O_RDONLY); + if (cachefd >= 0) + { + logf("skipping, cache already waiting for commit"); + close(cachefd); + return ; + } + + cachefd = open(TAGCACHE_FILE_TEMP, O_RDWR | O_CREAT | O_TRUNC); + if (cachefd < 0) + { + logf("master file open failed"); + return ; + } + + snprintf(buf, sizeof buf, TAGCACHE_FILE_INDEX, tag_filename); + filenametag_fd = open(buf, O_RDONLY); + + if (filenametag_fd >= 0) + { + if (read(filenametag_fd, &header, sizeof(struct tagcache_header)) != + sizeof(struct tagcache_header) || header.magic != TAGCACHE_MAGIC) + { + logf("header error"); + close(filenametag_fd); + filenametag_fd = -1; + } + } + + + cpu_boost(true); + + /* Scan for new files. */ + memset(&header, 0, sizeof(struct tagcache_header)); + write(cachefd, &header, sizeof(struct tagcache_header)); + + //strcpy(curpath, "/Best"); + ret = check_dir("/"); + + /* Write the header. */ + header.magic = TAGCACHE_MAGIC; + header.datasize = data_size; + header.entry_count = total_entry_count; + lseek(cachefd, 0, SEEK_SET); + write(cachefd, &header, sizeof(struct tagcache_header)); + close(cachefd); + + if (filenametag_fd >= 0) + { + close(filenametag_fd); + filenametag_fd = -1; + } + + if (!ret) + { + logf("Aborted."); + cpu_boost(false); + return ; + } + + /* Commit changes to the database. */ + if (commit()) + { + remove(TAGCACHE_FILE_TEMP); + logf("tagcache built!"); + } + + cpu_boost(false); +} + +#ifdef HAVE_TC_RAMCACHE +static void load_ramcache(void) +{ + if (!hdr) + return ; + + cpu_boost(true); + + /* At first we should load the cache (if exists). */ + ramcache = load_tagcache(); + + if (!ramcache) + { + hdr = NULL; + remove_files(); + } + + cpu_boost(false); +} +#endif + +static void tagcache_thread(void) +{ + struct event ev; + bool check_done = false; + + while (1) + { + queue_wait_w_tmo(&tagcache_queue, &ev, HZ); + + switch (ev.id) + { + case Q_START_SCAN: + check_done = false; + break ; + + case Q_FORCE_UPDATE: + //remove_files(); + build_tagcache(); + break ; + +#ifdef HAVE_TC_RAMCACHE + case SYS_TIMEOUT: + if (check_done || !dircache_is_enabled()) + break ; + + if (!ramcache && global_settings.tagcache_ram) + load_ramcache(); + + if (global_settings.tagcache_ram) + build_tagcache(); + + check_done = true; + break ; +#endif + + case Q_STOP_SCAN: + break ; + + case SYS_POWEROFF: + break ; + +#ifndef SIMULATOR + case SYS_USB_CONNECTED: + usb_acknowledge(SYS_USB_CONNECTED_ACK); + usb_wait_for_disconnect(&tagcache_queue); + break ; +#endif + } + } +} + +int tagcache_get_progress(void) +{ + int total_count = -1; + +#ifdef HAVE_DIRCACHE + if (dircache_is_enabled()) + { + total_count = dircache_get_entry_count(); + } + else + { + if (hdr) + total_count = hdr->h.entry_count; + } +#endif + + if (total_count < 0) + return -1; + + return processed_dir_count * 100 / total_count; +} + +void tagcache_start_scan(void) +{ + queue_post(&tagcache_queue, Q_START_SCAN, 0); +} + +bool tagcache_force_update(void) +{ + queue_post(&tagcache_queue, Q_FORCE_UPDATE, 0); + gui_syncsplash(HZ*2, true, str(LANG_TAGCACHE_FORCE_UPDATE_SPLASH)); + + return false; +} + +void tagcache_stop_scan(void) +{ + queue_post(&tagcache_queue, Q_STOP_SCAN, 0); +} + +#ifdef HAVE_TC_RAMCACHE +bool tagcache_is_ramcache(void) +{ + return ramcache; +} +#endif + +void tagcache_init(void) +{ + /* If the previous cache build/update was interrupted, commit + * the changes first. */ + cpu_boost(true); + allocate_tempbuf(); + commit(); + free_tempbuf(); + cpu_boost(false); + +#ifdef HAVE_TC_RAMCACHE + /* Allocate space for the tagcache if found on disk. */ + allocate_tagcache(); +#endif + + queue_init(&tagcache_queue); + create_thread(tagcache_thread, tagcache_stack, + sizeof(tagcache_stack), tagcache_thread_name); +} + + diff --git a/apps/tagcache.h b/apps/tagcache.h new file mode 100644 index 0000000000..a405764644 --- /dev/null +++ b/apps/tagcache.h @@ -0,0 +1,78 @@ +/*************************************************************************** + * __________ __ ___. + * 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 _TAGCACHE_H +#define _TAGCACHE_H + +#include "id3.h" + +enum tag_type { tag_artist = 0, tag_album, tag_genre, tag_title, + tag_filename/*, tag_checksum*/ }; +#define TAG_COUNT 5 + +#ifdef HAVE_DIRCACHE +#define HAVE_TC_RAMCACHE 1 +#endif + +/* Allow a little drift to the filename ordering. */ +#define POS_HISTORY_COUNT 4 + +/* How many entries we can create in one tag file (for sorting). */ +#define TAGFILE_MAX_ENTRIES 20000 + +#define SEEK_LIST_SIZE 50 +#define TAGCACHE_MAX_FILTERS 3 + +struct tagcache_search { + /* For internal use only. */ + int fd; + long seek_list[SEEK_LIST_SIZE]; + long filter_tag[TAGCACHE_MAX_FILTERS]; + long filter_seek[TAGCACHE_MAX_FILTERS]; + int filter_count; + int seek_list_count; + int seek_pos; + long position; + int entry_count; + bool valid; + + /* Exported variables. */ + bool ramsearch; + int type; + char *result; + int result_len; + long result_seek; +}; + +bool tagcache_fill_tags(struct mp3entry *id3, const char *filename); +bool tagcache_search(struct tagcache_search *tcs, int tag); +bool tagcache_search_add_filter(struct tagcache_search *tcs, + int tag, int seek); +bool tagcache_get_next(struct tagcache_search *tcs); +void tagcache_search_finish(struct tagcache_search *tcs); + +int tagcache_get_progress(void); +#ifdef HAVE_TC_RAMCACHE +bool tagcache_is_ramcache(void); +#endif +void tagcache_init(void); +void tagcache_start_scan(void); +void tagcache_stop_scan(void); +bool tagcache_force_update(void); + +#endif diff --git a/apps/tagtree.c b/apps/tagtree.c new file mode 100644 index 0000000000..cb4135c204 --- /dev/null +++ b/apps/tagtree.c @@ -0,0 +1,536 @@ +/*************************************************************************** + * __________ __ ___. + * 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. + * + ****************************************************************************/ + +/** + * Basic structure on this file was copied from dbtree.c and modified to + * support the tag cache interface. + */ +#include +#include +#include +#include "system.h" +#include "kernel.h" +#include "splash.h" +#include "icons.h" +#include "tree.h" +#include "settings.h" +#include "tagcache.h" +#include "tagtree.h" +#include "lang.h" +#include "logf.h" +#include "playlist.h" +#include "keyboard.h" +#include "gui/list.h" + +#define CHUNKED_NEXT -2 + +static int tagtree_play_folder(struct tree_context* c); +static int tagtree_search(struct tree_context* c, char* string); + +static char searchstring[32]; +struct tagentry { + char *name; + int newtable; + int extraseek; +}; + +static struct tagcache_search tcs; + +int tagtree_load(struct tree_context* c) +{ + int i; + int namebufused = 0; + struct tagentry *dptr = (struct tagentry *)c->dircache; + + int table = c->currtable; + int extra = c->currextra; + int extra2 = c->currextra2; + + c->dentry_size = sizeof(struct tagentry); + + if (!table) + { + c->dirfull = false; + table = root; + c->currtable = table; + } + + if (c->dirfull) + table = chunked_next; + + switch (table) { + case root: { + static const int tables[] = {allartists, allalbums, allgenres, allsongs, + search }; + unsigned char* labels[] = { str(LANG_ID3DB_ARTISTS), + str(LANG_ID3DB_ALBUMS), + str(LANG_ID3DB_GENRES), + str(LANG_ID3DB_SONGS), + str(LANG_ID3DB_SEARCH)}; + + for (i = 0; i < 5; i++) { + dptr->name = &c->name_buffer[namebufused]; + dptr->newtable = tables[i]; + strcpy(dptr->name, (char *)labels[i]); + namebufused += strlen(dptr->name) + 1; + dptr++; + } + c->dirlength = c->filesindir = i; + return i; + } + + case search: { + static const int tables[] = {searchartists, + searchalbums, + searchsongs}; + unsigned char* labels[] = { str(LANG_ID3DB_SEARCH_ARTISTS), + str(LANG_ID3DB_SEARCH_ALBUMS), + str(LANG_ID3DB_SEARCH_SONGS)}; + + for (i = 0; i < 3; i++) { + dptr->name = &c->name_buffer[namebufused]; + dptr->newtable = tables[i]; + strcpy(dptr->name, (char *)labels[i]); + namebufused += strlen(dptr->name) + 1; + dptr++; + } + c->dirlength = c->filesindir = i; + return i; + } + + case searchartists: + case searchalbums: + case searchsongs: + i = tagtree_search(c, searchstring); + c->dirlength = c->filesindir = i; + if (c->dirfull) { + gui_syncsplash(HZ, true, (unsigned char *)"%s %s", + str(LANG_SHOWDIR_ERROR_BUFFER), + str(LANG_SHOWDIR_ERROR_FULL)); + c->dirfull = false; + } + else + gui_syncsplash(HZ, true, str(LANG_ID3DB_MATCHES), i); + return i; + + case allsongs: + logf("songs.."); + tagcache_search(&tcs, tag_title); + break; + + case allgenres: + logf("genres.."); + tagcache_search(&tcs, tag_genre); + break; + + case allalbums: + logf("albums.."); + tagcache_search(&tcs, tag_album); + break; + + case allartists: + logf("artists.."); + tagcache_search(&tcs, tag_artist); + break; + + case artist4genres: + logf("artist4genres.."); + tagcache_search(&tcs, tag_artist); + tagcache_search_add_filter(&tcs, tag_genre, extra); + break; + + case albums4artist: + logf("albums4artist.."); + tagcache_search(&tcs, tag_album); + tagcache_search_add_filter(&tcs, tag_artist, extra); + break; + + case songs4album: + logf("songs4album.."); + tagcache_search(&tcs, tag_title); + tagcache_search_add_filter(&tcs, tag_album, extra); + if (extra2 > 0) + tagcache_search_add_filter(&tcs, tag_artist, extra2); + break; + + case songs4artist: + logf("songs4artist.."); + tagcache_search(&tcs, tag_title); + tagcache_search_add_filter(&tcs, tag_artist, extra); + break; + + case chunked_next: + logf("chunked next..."); + break; + + default: + logf("Unsupported table %d\n", table); + return -1; + } + + i = 0; + namebufused = 0; + c->dirfull = false; + while (tagcache_get_next(&tcs)) + { + dptr->newtable = tcs.result_seek; + if (!tcs.ramsearch) + { + dptr->name = &c->name_buffer[namebufused]; + namebufused += tcs.result_len; + if (namebufused > c->name_buffer_size) + { + logf("buffer full, 1 entry missed."); + c->dirfull = true; + break ; + } + strcpy(dptr->name, tcs.result); + } + else + dptr->name = tcs.result; + dptr++; + i++; + + /** + * Estimate when we are running out of space so we can stop + * and enabled chunked browsing without missing entries. + */ + if (i >= global_settings.max_files_in_dir - 1 + || namebufused + 200 > c->name_buffer_size) + { + c->dirfull = true; + break ; + } + + } + + if (c->dirfull) + { + dptr->name = "===>"; + dptr->newtable = chunked_next; + dptr++; + i++; + } + else + tagcache_search_finish(&tcs); + + c->dirlength = c->filesindir = i; + + return i; +} + +static int tagtree_search(struct tree_context* c, char* string) +{ + struct tagentry *dptr = (struct tagentry *)c->dircache; + int hits = 0; + int namebufused = 0; + + switch (c->currtable) { + case searchartists: + tagcache_search(&tcs, tag_artist); + break; + + case searchalbums: + tagcache_search(&tcs, tag_album); + break; + + case searchsongs: + tagcache_search(&tcs, tag_title); + break; + + default: + logf("Invalid table %d\n", c->currtable); + return 0; + } + + while (tagcache_get_next(&tcs)) + { + if (!strcasestr(tcs.result, string)) + continue ; + + if (!tcs.ramsearch) + { + dptr->name = &c->name_buffer[namebufused]; + namebufused += tcs.result_len; + strcpy(dptr->name, tcs.result); + } + else + dptr->name = tcs.result; + + dptr->newtable = tcs.result_seek; + dptr++; + hits++; + } + + tagcache_search_finish(&tcs); + + return hits; +} + +int tagtree_enter(struct tree_context* c) +{ + int rc = 0; + struct tagentry *dptr = (struct tagentry *)c->dircache; + int newextra; + + dptr += c->selected_item; + + if (dptr->newtable == chunked_next) + { + c->selected_item=0; + gui_synclist_select_item(&tree_lists, c->selected_item); + return 0; + } + + c->dirfull = false; + newextra = dptr->newtable; + + if (c->dirlevel >= MAX_DIR_LEVELS) + return 0; + + c->selected_item_history[c->dirlevel]=c->selected_item; + c->table_history[c->dirlevel] = c->currtable; + c->extra_history[c->dirlevel] = c->currextra; + c->pos_history[c->dirlevel] = c->firstpos; + c->dirlevel++; + + switch (c->currtable) { + case root: + c->currtable = newextra; + c->currextra = newextra; + break; + + case allartists: + case searchartists: + c->currtable = albums4artist; + c->currextra = newextra; + break; + + case allgenres: + c->currtable = artist4genres; + c->currextra = newextra; + break; + + case artist4genres: + c->currtable = albums4artist; + c->currextra = newextra; + break; + + case allalbums: + c->currtable = songs4album; + c->currextra = newextra; + c->currextra2 = -1; + break; + case albums4artist: + case searchalbums: + c->currtable = songs4album; + c->currextra2 = c->currextra; + c->currextra = newextra; + break; + + case allsongs: + case songs4album: + case songs4artist: + case searchsongs: + c->dirlevel--; + if (tagtree_play_folder(c) >= 0) + rc = 2; + break; + + case search: + rc = kbd_input(searchstring, sizeof(searchstring)); + if (rc == -1 || !searchstring[0]) + c->dirlevel--; + else + c->currtable = newextra; + break; + + default: + c->dirlevel--; + break; + } + c->selected_item=0; + gui_synclist_select_item(&tree_lists, c->selected_item); + + return rc; +} + +void tagtree_exit(struct tree_context* c) +{ + c->dirfull = false; + c->dirlevel--; + c->selected_item=c->selected_item_history[c->dirlevel]; + gui_synclist_select_item(&tree_lists, c->selected_item); + c->currtable = c->table_history[c->dirlevel]; + c->currextra = c->extra_history[c->dirlevel]; + c->firstpos = c->pos_history[c->dirlevel]; + + /* Just to be sure when chunked browsing is used. */ + tagcache_search_finish(&tcs); +} + +int tagtree_get_filename(struct tree_context* c, char *buf, int buflen) +{ + struct tagentry *entry = (struct tagentry *)c->dircache; + + entry += c->selected_item; + + tagcache_search(&tcs, tag_filename); + tagcache_search_add_filter(&tcs, tag_title, entry->newtable); + + if (!tagcache_get_next(&tcs)) + { + tagcache_search_finish(&tcs); + return -1; + } + + strncpy(buf, tcs.result, buflen-1); + tagcache_search_finish(&tcs); + + return 0; +} + +#if 0 +bool tagtree_rename_tag(struct tree_context *c, const char *newtext) +{ + struct tagentry *dptr = (struct tagentry *)c->dircache; + int extra, extra2; + int type; + + dptr += c->selected_item; + extra = dptr->newtable; + extra2 = dptr->extraseek; + + switch (c->currtable) { + case allgenres: + tagcache_search(&tcs, tag_title); + tagcache_search_add_filter(&tcs, tag_genre, extra); + type = tag_genre; + break; + + case allalbums: + tagcache_search(&tcs, tag_title); + tagcache_search_add_filter(&tcs, tag_album, extra); + type = tag_album; + break; + + case allartists: + tagcache_search(&tcs, tag_title); + tagcache_search_add_filter(&tcs, tag_artist, extra); + type = tag_artist; + break; + + case artist4genres: + tagcache_search(&tcs, tag_title); + tagcache_search_add_filter(&tcs, tag_genre, extra); + type = tag_artist; + break; + + case albums4artist: + tagcache_search(&tcs, tag_title); + tagcache_search_add_filter(&tcs, tag_album, extra); + tagcache_search_add_filter(&tcs, tag_artist, extra2); + type = tag_album; + break; + + default: + logf("wrong table"); + return false; + } + + while (tagcache_get_next(&tcs)) + { + // tagcache_modify(&tcs, type, newtext); + } + + tagcache_search_finish(&tcs); + return true; +} +#endif + +static int tagtree_play_folder(struct tree_context* c) +{ + struct tagentry *entry = (struct tagentry *)c->dircache; + int i; + + if (playlist_create(NULL, NULL) < 0) { + logf("Failed creating playlist\n"); + return -1; + } + +#ifdef HAVE_ADJUSTABLE_CPU_FREQ + cpu_boost(true); +#endif + + for (i=0; i < c->filesindir; i++) { + tagcache_search(&tcs, tag_filename); + tagcache_search_add_filter(&tcs, tag_title, entry[i].newtable); + + if (!tagcache_get_next(&tcs)) + { + tagcache_search_finish(&tcs); + continue ; + } + playlist_insert_track(NULL, tcs.result, PLAYLIST_INSERT, false); + tagcache_search_finish(&tcs); + } + +#ifdef HAVE_ADJUSTABLE_CPU_FREQ + cpu_boost(false); +#endif + + if (global_settings.playlist_shuffle) + c->selected_item = playlist_shuffle(current_tick, c->selected_item); + if (!global_settings.play_selected) + c->selected_item = 0; + gui_synclist_select_item(&tree_lists, c->selected_item); + + playlist_start(c->selected_item,0); + + return 0; +} + +#ifdef HAVE_LCD_BITMAP +const char* tagtree_get_icon(struct tree_context* c) +#else +int tagtree_get_icon(struct tree_context* c) +#endif +{ + int icon; + + switch (c->currtable) + { + case allsongs: + case songs4album: + case songs4artist: + case searchsongs: + icon = Icon_Audio; + break; + + default: + icon = Icon_Folder; + break; + } + +#ifdef HAVE_LCD_BITMAP + return (char *)bitmap_icons_6x8[icon]; +#else + return icon; +#endif +} diff --git a/apps/tagtree.h b/apps/tagtree.h new file mode 100644 index 0000000000..7eafb7061d --- /dev/null +++ b/apps/tagtree.h @@ -0,0 +1,41 @@ +/*************************************************************************** + * __________ __ ___. + * 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 _TAGTREE_H +#define _TAGTREE_H + +#include "tagcache.h" +#include "tree.h" + +enum table { invalid, root, allsongs, allalbums, allartists, allgenres, + albums4artist, songs4album, songs4artist, artist4genres, + search, searchartists, searchalbums, searchsongs, + chunked_next }; + +int tagtree_enter(struct tree_context* c); +void tagtree_exit(struct tree_context* c); +int tagtree_load(struct tree_context* c); +#ifdef HAVE_LCD_BITMAP +const char* tagtree_get_icon(struct tree_context* c); +#else +int tagtree_get_icon(struct tree_context* c); +#endif +int tagtree_get_filename(struct tree_context* c, char *buf, int buflen); + +#endif + diff --git a/apps/tree.c b/apps/tree.c index b11d28f1f8..5efacd5993 100644 --- a/apps/tree.c +++ b/apps/tree.c @@ -57,10 +57,11 @@ #include "filetypes.h" #include "misc.h" #include "filetree.h" -#include "dbtree.h" +#include "tagtree.h" #include "recorder/recording.h" #include "rtc.h" #include "dircache.h" +#include "tagcache.h" #include "yesno.h" /* gui api */ @@ -164,7 +165,8 @@ char * tree_get_filename(int selected_item, void * data, char *buffer) bool id3db = *(local_tc->dirfilter) == SHOW_ID3DB; if (id3db) { - name = ((char**)local_tc->dircache)[selected_item * local_tc->dentry_size]; + char **buf = local_tc->dircache; + name = buf[selected_item * (local_tc->dentry_size/sizeof(int))]; } else { struct entry* dc = local_tc->dircache; @@ -189,7 +191,7 @@ void tree_get_fileicon(int selected_item, void * data, ICON * icon) struct tree_context * local_tc=(struct tree_context *)data; bool id3db = *(local_tc->dirfilter) == SHOW_ID3DB; if (id3db) { - *icon = (ICON)db_get_icon(&tc); + *icon = (ICON)tagtree_get_icon(&tc); } else { struct entry* dc = local_tc->dircache; @@ -267,6 +269,7 @@ struct tree_context* tree_get_context(void) 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++) { @@ -292,7 +295,7 @@ static int update_dir(void) tc.currextra != lastextra || tc.firstpos != lastfirstpos) { - if (db_load(&tc) < 0) + if (tagtree_load(&tc) < 0) return -1; lasttable = tc.currtable; @@ -494,7 +497,7 @@ static bool check_changed_id3mode(bool currmode) currmode = global_settings.dirfilter == SHOW_ID3DB; if (currmode) { curr_context=CONTEXT_ID3DB; - db_load(&tc); + tagtree_load(&tc); } else { @@ -600,7 +603,7 @@ static bool dirbrowse(void) if ( numentries == 0 ) break; - switch (id3db?db_enter(&tc):ft_enter(&tc)) + switch (id3db?tagtree_enter(&tc):ft_enter(&tc)) { case 1: reload_dir = true; break; case 2: start_wps = true; break; @@ -624,7 +627,7 @@ static bool dirbrowse(void) break; if (id3db) - db_exit(&tc); + tagtree_exit(&tc); else if (ft_exit(&tc) == 3) exit_func = true; @@ -732,6 +735,7 @@ static bool dirbrowse(void) restore = true; id3db = check_changed_id3mode(id3db); + reload_dir = true; break; } @@ -772,7 +776,7 @@ static bool dirbrowse(void) case songs4artist: case searchsongs: attr=TREE_ATTR_MPA; - db_get_filename(&tc, buf, sizeof(buf)); + tagtree_get_filename(&tc, buf, sizeof(buf)); break; } } @@ -906,6 +910,7 @@ static bool dirbrowse(void) lastextra = -1; reload_root = false; } + if (! reload_dir ) { gui_synclist_select_item(&tree_lists, 0); @@ -935,6 +940,7 @@ static bool dirbrowse(void) need_update = true; reload_dir = false; } + if(need_update) { tc.selected_item = gui_synclist_get_sel_pos(&tree_lists); need_update=false; @@ -1177,8 +1183,6 @@ void tree_init(void) memset(&tc, 0, sizeof(tc)); tc.dirfilter = &global_settings.dirfilter; - tagdb_init(); - tc.name_buffer_size = AVERAGE_FILENAME_LENGTH * max_files; tc.name_buffer = buffer_alloc(tc.name_buffer_size); @@ -1331,9 +1335,9 @@ void ft_play_filename(char *dir, char *file) /* These two functions are called by the USB and shutdown handlers */ void tree_flush(void) { - rundb_shutdown(); - tagdb_shutdown(); + tagcache_stop_scan(); playlist_shutdown(); + #ifdef HAVE_DIRCACHE if (global_settings.dircache) { @@ -1351,8 +1355,6 @@ void tree_flush(void) void tree_restore(void) { - tagdb_init(); - rundb_init(); #ifdef HAVE_DIRCACHE if (global_settings.dircache) { @@ -1376,5 +1378,6 @@ void tree_restore(void) gui_textarea_clear(&screens[i]); } } + tagcache_start_scan(); #endif } diff --git a/apps/tree.h b/apps/tree.h index 9b4888c46a..fe903250d7 100644 --- a/apps/tree.h +++ b/apps/tree.h @@ -213,6 +213,7 @@ struct tree_context { int extra_history[MAX_DIR_LEVELS]; /* db use */ int currtable; /* db use */ int currextra; /* db use */ + int currextra2; /* db use */ /* A big buffer with plenty of entry structs, * contains all files and dirs in the current * dir (with filters applied) */ diff --git a/firmware/common/dircache.c b/firmware/common/dircache.c index 4facbb8ab2..f9fd63b8e4 100644 --- a/firmware/common/dircache.c +++ b/firmware/common/dircache.c @@ -84,8 +84,8 @@ static struct dircache_entry* allocate_entry(void) /* Make sure the entry is long aligned. */ if ((long)next_entry & 0x03) { - next_entry = (struct dircache_entry *)(((long)next_entry & ~0x03) + 0x04); dircache_size += 4 - ((long)next_entry & 0x03); + next_entry = (struct dircache_entry *)(((long)next_entry & ~0x03) + 0x04); } #endif next_entry->name_len = 0; @@ -95,7 +95,6 @@ static struct dircache_entry* allocate_entry(void) next_entry->next = NULL; dircache_size += sizeof(struct dircache_entry); - entry_count++; return next_entry; } @@ -186,6 +185,7 @@ static int dircache_scan(struct travel_data *td) td->ce->wrttime = td->entry.wrttime; memcpy(td->ce->d_name, td->entry.name, td->ce->name_len); dircache_size += td->ce->name_len; + entry_count++; if (td->entry.attr & FAT_ATTR_DIRECTORY) { -- cgit